Merge remote-tracking branch 'upstream/main'

This commit is contained in:
misha
2026-02-10 12:38:08 +03:00
88 changed files with 1161 additions and 610 deletions

View File

@@ -3,7 +3,7 @@ name: Eatvio App Android Test
on: [ pull_request ]
env:
flutter_version: "3.38.6"
flutter_version: "3.38.9"
java_version: "21.x"
flutter_channel: "stable"

View File

@@ -3,7 +3,7 @@ name: Eatvio App Android Test
on: [ pull_request ]
env:
flutter_version: "3.38.6"
flutter_version: "3.38.9"
java_version: "21.x"
flutter_channel: "stable"
my_local_cache_dir: "/opt/hostedtoolcache/"

View File

@@ -1 +1,94 @@
{"agcgw":{"url":"connect-drru.hispace.dbankcloud.ru","backurl":"connect-drru.hispace.dbankcloud.cn","websocketurl":"connect-ws-drru.hispace.dbankcloud.ru","websocketbackurl":"connect-ws-drru.hispace.dbankcloud.cn"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"20007000036693451","product_id":"461323198429446894","client_id":"1595824543740483264","client_secret":"2A1727B0D31AA9BE1F5D9E8A5CDA1A67B9EF084AC9FB68D6C633A3CCB08ECDEE","project_id":"461323198429446894","app_id":"113238569","api_key":"DQEDAJNRITp9M3uc1rPHyT8PzLc7bnVcKJAeavZZk0TQNMx9ifvBebr+JOmZe9tEvjnLwv7c1hAHXmPGR/gWG86iABDrCVotq+NGnw==","package_name":"com.eatvioapp.app"},"oauth_client":{"client_id":"113238569","client_type":1},"app_info":{"app_id":"113238569","package_name":"com.eatvioapp.app"},"service":{"analytics":{"collector_url":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drru.ai.dbankcloud.ru,ml-api-drru.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drru.cloud.huawei.ru"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"RU","configuration_version":"3.0","appInfos":[{"package_name":"com.eatvioapp.app","client":{"app_id":"113238569"},"app_info":{"package_name":"com.eatvioapp.app","app_id":"113238569"},"oauth_client":{"client_type":1,"client_id":"113238569"}}]}
{
"agcgw" : {
"url" : "connect-drru.hispace.dbankcloud.ru",
"backurl" : "connect-drru.hispace.dbankcloud.cn",
"websocketurl" : "connect-ws-drru.hispace.dbankcloud.ru",
"websocketbackurl" : "connect-ws-drru.hispace.dbankcloud.cn"
},
"agcgw_all" : {
"SG" : "connect-dra.dbankcloud.cn",
"SG_back" : "connect-dra.hispace.hicloud.com",
"CN" : "connect-drcn.dbankcloud.cn",
"CN_back" : "connect-drcn.hispace.hicloud.com",
"RU" : "connect-drru.hispace.dbankcloud.ru",
"RU_back" : "connect-drru.hispace.dbankcloud.cn",
"DE" : "connect-dre.dbankcloud.cn",
"DE_back" : "connect-dre.hispace.hicloud.com"
},
"websocketgw_all" : {
"SG" : "connect-ws-dra.hispace.dbankcloud.cn",
"SG_back" : "connect-ws-dra.hispace.dbankcloud.com",
"CN" : "connect-ws-drcn.hispace.dbankcloud.cn",
"CN_back" : "connect-ws-drcn.hispace.dbankcloud.com",
"RU" : "connect-ws-drru.hispace.dbankcloud.ru",
"RU_back" : "connect-ws-drru.hispace.dbankcloud.cn",
"DE" : "connect-ws-dre.hispace.dbankcloud.cn",
"DE_back" : "connect-ws-dre.hispace.dbankcloud.com"
},
"client" : {
"cp_id" : "20007000036693451",
"product_id" : "461323198429446894",
"client_id" : "1595824543740483264",
"client_secret" : "2A1727B0D31AA9BE1F5D9E8A5CDA1A67B9EF084AC9FB68D6C633A3CCB08ECDEE",
"project_id" : "461323198429446894",
"app_id" : "113238569",
"api_key" : "DQEDAJNRITp9M3uc1rPHyT8PzLc7bnVcKJAeavZZk0TQNMx9ifvBebr+JOmZe9tEvjnLwv7c1hAHXmPGR/gWG86iABDrCVotq+NGnw==",
"package_name" : "com.eatvioapp.app"
},
"oauth_client" : {
"client_id" : "113238569",
"client_type" : 1
},
"app_info" : {
"app_id" : "113238569",
"package_name" : "com.eatvioapp.app"
},
"service" : {
"analytics" : {
"collector_url" : "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
"collector_url_cn" : "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
"collector_url_de" : "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"collector_url_ru" : "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
"collector_url_sg" : "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
"resource_id" : "p1",
"channel_id" : ""
},
"ml" : {
"mlservice_url" : "ml-api-drru.ai.dbankcloud.ru,ml-api-drru.ai.dbankcloud.cn"
},
"cloudstorage" : {
"storage_url" : "https://agc-storage-drcn.platform.dbankcloud.cn",
"storage_url_ru" : "https://agc-storage-drru.cloud.huawei.ru",
"storage_url_sg" : "https://ops-dra.agcstorage.link",
"storage_url_de" : "https://ops-dre.agcstorage.link",
"storage_url_cn" : "https://agc-storage-drcn.platform.dbankcloud.cn",
"storage_url_ru_back" : "https://agc-storage-drru.cloud.huawei.ru",
"storage_url_sg_back" : "https://agc-storage-dra.cloud.huawei.asia",
"storage_url_de_back" : "https://agc-storage-dre.cloud.huawei.eu",
"storage_url_cn_back" : "https://agc-storage-drcn.cloud.huawei.com.cn"
},
"search" : {
"url" : "https://search-drru.cloud.huawei.ru"
},
"edukit" : {
"edu_url" : "edukit.cloud.huawei.com.cn",
"dh_url" : "edukit.cloud.huawei.com.cn"
}
},
"region" : "RU",
"configuration_version" : "3.0",
"appInfos" : [ {
"package_name" : "com.eatvioapp.app",
"client" : {
"app_id" : "113238569"
},
"app_info" : {
"package_name" : "com.eatvioapp.app",
"app_id" : "113238569"
},
"oauth_client" : {
"client_type" : 1,
"client_id" : "113238569"
}
} ]
}

View File

@@ -21,6 +21,12 @@
<meta-data
android:name="push_kit_auto_init_enabled"
android:value="true" />
<meta-data
android:name="com.huawei.hms.client.appid"
android:value="appid=113238569" />
<meta-data
android:name="com.huawei.hms.client.cpid"
android:value="cpid=20007000036693451" />
<activity
android:name=".MainActivity"
android:exported="true"

View File

@@ -47,17 +47,21 @@ class MainActivity: FlutterActivity() {
}
if (appId.isNullOrBlank()) {
android.util.Log.e("HMS", "Missing Huawei appId. Ensure agconnect-services.json or manifest meta-data is configured.")
runOnUiThread {
result.error("HMS_TOKEN_ERROR", "Missing Huawei appId. Ensure agconnect-services.json or manifest meta-data is configured.", null)
}
return@launch
}
android.util.Log.i("HMS", "Getting token for appId: $appId")
val token = HmsInstanceId.getInstance(applicationContext)
.getToken(appId, "HCM")
android.util.Log.i("HMS", "Token retrieved successfully")
runOnUiThread {
result.success(token)
}
} catch (e: Exception) {
android.util.Log.e("HMS", "Error getting HMS token: ${e.message}", e)
runOnUiThread {
result.error("HMS_TOKEN_ERROR", e.message, null)
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,6 @@
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;
@@ -9,7 +10,8 @@ class CommentsRepository {
Future<void> deleteComment({required int id}) async {
try {
return service.deleteComment(id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -17,7 +19,8 @@ class CommentsRepository {
Future<void> updateComment({required int id, required String body}) async {
try {
return service.updateComment(id, CommentUpdateModel(body: body));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -69,4 +69,9 @@ class AppConstants {
static const String usersEndpoint = '/users';
static const String postsEndpoint = '/posts';
static const String commentsEndpoint = '/comments';
// Delay of pending requests
/// 30 секунд стоит потому что запрос может идти 12-15 секунд
/// Особенно если производится отладка на сервере разработчика
static const Duration pendingDelay = Duration(seconds: 30);
}

View File

@@ -1,10 +1,12 @@
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/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:shared_preferences/shared_preferences.dart';
import 'package:sentry_dio/sentry_dio.dart';
/// Регистрация внешних зависимостей в системе внедрения зависимостей
class ExternalRegistrations {
@@ -19,15 +21,16 @@ class ExternalRegistrations {
/// Регистрация Dio HTTP клиента
static void _registerDio() {
sl.registerLazySingleton(
() =>
Dio(BaseOptions(
baseUrl: sl<AppConfig>().baseUrl,
headers: {'accept': 'application/json', 'content-type': 'application/json'},
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
sendTimeout: const Duration(seconds: 10),
))
..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]),
() => Dio(
BaseOptions(
baseUrl: sl<AppConfig>().baseUrl,
headers: {'accept': 'application/json', 'content-type': 'application/json'},
connectTimeout: AppConstants.pendingDelay,
receiveTimeout: AppConstants.pendingDelay,
sendTimeout: AppConstants.pendingDelay,
),
)..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()])
..addSentry(),
);
}

View File

@@ -12,6 +12,7 @@ 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';
class FirebaseApi {
final _firebaseMessaging = FirebaseMessaging.instance;
@@ -49,7 +50,7 @@ class FirebaseApi {
await sl<Dio>().post(
'/api/anonymous_token',
data: {
'platform': Platform.isIOS ? 'ios' : 'android',
'platform': Platform.isIOS ? 'ios' : (await _isHuaweiDevice()) ? 'huawei' : 'android',
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
'token': await getToken(),
},
@@ -61,6 +62,7 @@ class FirebaseApi {
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
// Не пробрасываем ошибку дальше, так как это не критичная операция
}
}
@@ -85,6 +87,7 @@ class FirebaseApi {
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
// Не пробрасываем ошибку дальше, так как это не критичная операция
}
}
@@ -110,10 +113,12 @@ class FirebaseApi {
} on PlatformException catch (e, st) {
showErrorToast('$e $st');
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
return null;
} catch (e, st) {
showErrorToast('$e $st');
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
return null;
}
}

View File

@@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:go_router/go_router.dart';
class TokenInterceptor extends Interceptor {
@@ -23,6 +25,7 @@ class TokenInterceptor extends Interceptor {
ErrorInterceptorHandler handler,
) async {
if (err.response?.statusCode == 401) {
await sl<WsManager>().clear();
await TokenStorage.removeTokenInStorage();
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
}

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
/// Helper class for managing themes and colors.
class ThemeHelper {
@@ -34,7 +34,8 @@ class ThemeHelper {
var colorScheme = _supportedColorScheme[_appTheme] ?? ColorSchemes.primaryColorScheme;
return ThemeData(
textTheme: GoogleFonts.mulishTextTheme(),
fontFamily: 'Mulish-Local',
textTheme: ThemeData.light().textTheme.apply(fontFamily: 'Mulish-Local'),
visualDensity: VisualDensity.standard,
colorScheme: colorScheme,
scaffoldBackgroundColor: colorScheme.onPrimary,

View File

@@ -16,6 +16,21 @@ class ValidationUtils {
return regex.hasMatch(phone);
}
/// Нормализация номера телефона (+79991234455)
static String normalizePhone(String phone) {
if (phone.isEmpty) return '';
final digits = phone.replaceAll(RegExp(r'[^\d]'), '');
if (digits.isEmpty) return '';
String normalized = digits;
if (digits.startsWith('8')) {
normalized = '7${digits.substring(1)}';
} else if (digits.length == 10) {
normalized = '7$digits';
}
return '+$normalized';
}
/// Валидация URL
static bool isValidUrl(String url) {
if (url.isEmpty) return false;

View File

@@ -1,5 +1,6 @@
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;
@@ -9,7 +10,8 @@ class ViewsRepository {
Future<void> viewContent({required ViewModel viewModel}) async {
try {
await viewsService.viewContent(viewModel: viewModel);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
@@ -8,6 +9,7 @@ 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/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
class WsManager {
@@ -22,6 +24,10 @@ class WsManager {
PrivateChannel? _privateMessagesChannel;
PrivateChannel? _privateDirectChatChannel;
StreamSubscription? _lifecycleSubscription;
StreamSubscription? _eventSubscription;
StreamSubscription? _connectionEstablishedSubscription;
Stream<ChannelReadEvent>? get newMessagesStream => _privateMessagesChannel?.bind('App\\Events\\ChatMessageUpdated');
Stream<ChannelReadEvent>? get newNotificationsStream => _privateNotificationChannel?.bind('Illuminate\\Notifications\\Events\\BroadcastNotificationCreated');
@@ -33,7 +39,7 @@ class WsManager {
Future<void> initialize() async {
if (client == null) {
try {
await TokenStorage.haveTokenInStorage();
if (!await TokenStorage.haveTokenInStorage()) return;
final wsUrl = await wsService.getWebsocket();
final options = PusherChannelsOptions.fromHost(
@@ -71,7 +77,7 @@ class WsManager {
}
}
client?.eventStream.listen((event) async {
_connectionEstablishedSubscription = client?.eventStream.listen((event) async {
if (event.name == 'pusher:connection_established') {
final data = event.data;
final socketId = extractSocketId(data);
@@ -87,21 +93,26 @@ class WsManager {
await _initPrivateNotificationsChannel();
await _initPrivateMessagesChannel();
client?.lifecycleStream.listen((data) {
_lifecycleSubscription = client?.lifecycleStream.listen((data) {
if (data == PusherChannelsClientLifeCycleState.establishedConnection) {
_unsubscribe();
_subscribe();
}
});
client?.eventStream.listen((event) {
EasyDebounce.debounce(
'searchChatRoom',
const Duration(milliseconds: 300),
() => sl<ChatsCubit>().getChatRooms(),
);
_eventSubscription = client?.eventStream.listen((event) {
log('WsManager received event: ${event.name}');
// Только если это не системные сообщения Pusher, обновляем список чатов
if (!event.name.startsWith('pusher:')) {
EasyDebounce.debounce(
'searchChatRoom',
const Duration(milliseconds: 300),
() => sl<ChatsCubit>().getChatRooms(),
);
}
});
_subscribe();
} catch (e, st) {
await Sentry.captureException(e, stackTrace: st);
log(e.toString(), stackTrace: st);
}
}
@@ -166,6 +177,12 @@ class WsManager {
Future<void> clear() async {
await client?.disconnect();
await _lifecycleSubscription?.cancel();
await _eventSubscription?.cancel();
await _connectionEstablishedSubscription?.cancel();
_lifecycleSubscription = null;
_eventSubscription = null;
_connectionEstablishedSubscription = null;
client = null;
userId = null;
_privateDirectChatChannel?.unsubscribe();

View File

@@ -27,7 +27,7 @@ class FeedIconButton extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
svgIcon ?? const SizedBox.shrink(),
?svgIcon,
if (icon != null)
Icon(
icon,

View File

@@ -16,6 +16,7 @@ import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widge
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/utils/validation_utils.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -330,6 +331,25 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: MediaQuery.of(context).size.width / 2 - 22,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Рост'),
const SizedBox(height: 4),
CustomTextFormField(
controller: heightController,
hintText: 'Введите рост',
keyboardType: TextInputType.number,
onChanged: (newValue) {},
hasError: heightError,
),
if (heightError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Рост должен быть от 100 до 250 см')],
],
),
),
const SizedBox(width: 12),
SizedBox(
width: MediaQuery.of(context).size.width / 2 - 22,
child: Column(
@@ -349,25 +369,6 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
],
),
),
const SizedBox(width: 12),
SizedBox(
width: MediaQuery.of(context).size.width / 2 - 22,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Рост'),
const SizedBox(height: 4),
CustomTextFormField(
controller: heightController,
hintText: 'Введите рост',
keyboardType: TextInputType.number,
onChanged: (newValue) {},
hasError: heightError,
),
if (heightError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Рост должен быть от 100 до 250 см')],
],
),
),
],
),
const SizedBox(height: 28),
@@ -538,7 +539,7 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
if (hasErrorWeightOrHeightError) return;
// Очищаем номер телефона от маски для отправки
final cleanPhone = phoneController.text.replaceAll(RegExp(r'[^\d+]'), '');
final cleanPhone = ValidationUtils.normalizePhone(phoneController.text);
if (clientID == null) return;
// Вызываем OAuth регистрацию
@@ -615,7 +616,7 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
final oauthCubit = CubitRegistrations.sl<OAuthCubit>();
// Всегда передаем и email и phone
final String email = emailController.text;
final String phone = phoneController.text.replaceAll(RegExp(r'[^\d+]'), '');
final String phone = ValidationUtils.normalizePhone(phoneController.text);
final response = await oauthCubit.requestOAuthVerification(email: email, phone: phone);
@@ -726,7 +727,7 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
// Всегда передаем и email и phone (полный набор данных от провайдера)
// Используем данные из формы, если они есть, иначе из authResult
final String email = emailController.text.isNotEmpty ? emailController.text : (authResult.email ?? '');
final String phone = phoneController.text.isNotEmpty ? phoneController.text.replaceAll(RegExp(r'[^\d+]'), '') : (authResult.phone ?? '');
final String phone = phoneController.text.isNotEmpty ? ValidationUtils.normalizePhone(phoneController.text) : ValidationUtils.normalizePhone(authResult.phone ?? '');
// Проверяем, что данные есть
if (email.isEmpty && phone.isEmpty) {

View File

@@ -7,6 +7,7 @@ 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;
@@ -18,7 +19,8 @@ class AuthRepository {
return await authService.getTokenByCredentials(
credentials: CredentialsModel(email: email, password: password),
);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -33,7 +35,8 @@ class AuthRepository {
return await authService.signInViaSocialMedia(
body: OAuthLoginRequest(provider: provider, accessToken: accessToken, refreshToken: refreshToken, clientId: clientId),
);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -72,7 +75,8 @@ class AuthRepository {
),
))['token']!
as String;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -89,7 +93,8 @@ class AuthRepository {
return await authService.requestOAuthVerification(body: body);
}
return null;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -134,8 +139,9 @@ class AuthRepository {
'AuthRepository: Sending OAuth verification confirm request: provider=$provider, verificationType=$verificationType, code=$code, email=$email, phone=$phone',
);
return await authService.confirmOAuthVerification(body: request);
} catch (e) {
} catch (e, s) {
log('AuthRepository: Error in confirmOAuthVerification: $e');
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class ResetPasswordRepository {
final ResetPasswordService resetPasswordService;
@@ -20,7 +21,8 @@ class ResetPasswordRepository {
} else {
return false;
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -46,7 +48,8 @@ class ResetPasswordRepository {
} else {
return false;
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -11,6 +11,7 @@ import 'package:eatvio_app/core/env_config.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';
@@ -41,7 +42,8 @@ class SocialAuthFacade {
return null;
}
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return null;
}
}
@@ -157,8 +159,9 @@ class SocialAuthFacade {
birthDate: yandexProfileData.birthday,
clientId: clientId,
);
} catch (e) {
} catch (e, s) {
log(e.toString());
await Sentry.captureException(e, stackTrace: s);
}
return null;
}
@@ -205,7 +208,8 @@ class SocialAuthFacade {
if (code == null || deviceId == null) return null;
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return null;
}
}

View File

@@ -1,6 +1,7 @@
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;
@@ -28,14 +29,16 @@ class BarcodeRepository {
}
throw Exception('Unexpected status code: ${response.statusCode}');
} on DioException catch (e) {
} on DioException catch (e, s) {
if (e.response?.statusCode == 202) {
// Если получен статус 202, значит запущена задача обработки
final taskStatus = BarcodeTaskStatusModel.fromJson(e.response!.data as Map<String, dynamic>);
return BarcodeResult.processing(taskStatus.taskId ?? '');
}
await Sentry.captureException(e, stackTrace: s);
rethrow;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -52,7 +55,8 @@ class BarcodeRepository {
} else {
return BarcodeTaskStatus.processing();
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -8,6 +8,7 @@ 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;
@@ -29,9 +30,14 @@ class BlogPostRepository {
if (image != null) 'image': await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name),
});
return await blogPostService.createBlogPost(
formData: formData,
);
try {
return await blogPostService.createBlogPost(
formData: formData,
);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<XFile?> downloadImage(String imageUrl) async {
@@ -54,8 +60,9 @@ class BlogPostRepository {
log('Ошибка загрузки изображения: ${response.statusCode}');
return null;
}
} catch (e) {
} catch (e, s) {
log('Произошла ошибка: $e');
await Sentry.captureException(e, stackTrace: s);
return null;
}
}
@@ -79,11 +86,23 @@ class BlogPostRepository {
: null,
});
return await blogPostService.updateBlogPost(
id: id,
formData: formData,
);
try {
return await blogPostService.updateBlogPost(
id: id,
formData: formData,
);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> deleteBlogPost(int id) async => await blogPostService.deleteBlogPost(id: id);
Future<void> deleteBlogPost(int id) async {
try {
await blogPostService.deleteBlogPost(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
}

View File

@@ -1,5 +1,6 @@
import 'package:badges/badges.dart' as badges;
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/env_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
@@ -19,10 +20,7 @@ import 'package:go_router/go_router.dart';
class BottomNavigationBarWidget extends StatefulWidget {
final Widget child;
const BottomNavigationBarWidget({
super.key,
required this.child,
});
const BottomNavigationBarWidget({super.key, required this.child});
@override
State<BottomNavigationBarWidget> createState() => _BottomNavigationBarWidgetState();
@@ -35,24 +33,9 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
List<String> _getPages(bool isConsultant) {
if (isConsultant) {
return [
'/my-profile-screen',
'/feed-screen',
'/diary-screen',
'/consultations-screen',
'/chat-select-screen',
'/notifications-screen',
'/menu-screen',
];
return ['/my-profile-screen', '/feed-screen', '/diary-screen', '/consultations-screen', '/chat-select-screen', '/notifications-screen', '/menu-screen'];
} else {
return [
'/my-profile-screen',
'/feed-screen',
'/diary-screen',
'/chat-select-screen',
'/notifications-screen',
'/menu-screen',
];
return ['/my-profile-screen', '/feed-screen', '/diary-screen', '/chat-select-screen', '/notifications-screen', '/menu-screen'];
}
}
@@ -83,6 +66,49 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
}
}
static const Color _devAccentColor = Color(0xFFF59E0B); // amber-500
Widget _buildLogoIcon() {
final isProduction = sl<AppConfig>().baseUrl == EnvConfig.baseUrl;
final logo = SvgPicture.asset(
ImageConstant.imgLogo,
height: 32,
colorFilter: isProduction ? null : const ColorFilter.mode(_devAccentColor, BlendMode.srcIn),
);
if (isProduction) return logo;
return Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(color: _devAccentColor.withValues(alpha: 0.6), width: 1.5),
color: _devAccentColor.withValues(alpha: 0.08),
),
child: logo,
),
Positioned(
top: -6,
right: -4,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: _devAccentColor,
borderRadius: BorderRadius.circular(4),
boxShadow: [BoxShadow(color: _devAccentColor.withValues(alpha: 0.4), blurRadius: 4, offset: const Offset(0, 1))],
),
child: const Text(
'DEV',
style: TextStyle(fontSize: 8, fontWeight: FontWeight.w700, color: Colors.white, letterSpacing: 0.3, height: 1.0),
),
),
),
],
);
}
@override
Widget build(BuildContext context) {
final s = S.of(context);
@@ -91,10 +117,10 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
if (state is ProfileAvatarStateLoaded) {
final profileModel = state.profile;
final isConsultant = profileModel.roles.contains('consultant');
// Инициализируем pages на основе profileModel
pages ??= _getPages(isConsultant);
return Scaffold(
body: widget.child,
bottomNavigationBar: BottomNavigationBar(
@@ -117,52 +143,26 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
),
BottomNavigationBarItem(
label: 'Лента',
icon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: 20,
height: 20,
ImageConstant.icnFeed,
),
),
activeIcon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnFeed,
width: 20,
height: 20,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
icon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn), width: 20, height: 20, ImageConstant.icnFeed),
),
activeIcon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(ImageConstant.icnFeed, width: 20, height: 20, colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)),
),
),
BottomNavigationBarItem(
label: s.diary,
icon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
ImageConstant.icnDiary,
width: 20,
height: 20,
),
),
activeIcon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnDiary,
width: 20,
height: 20,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
icon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn), ImageConstant.icnDiary, width: 20, height: 20),
),
activeIcon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(ImageConstant.icnDiary, width: 20, height: 20, colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)),
),
),
),
),
),
if (profileModel.roles.contains('consultant'))
BottomNavigationBarItem(
label: 'Консультации',
@@ -181,97 +181,69 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
'assets/icons/consultations.svg',
width: 20,
height: 20,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
),
colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
),
),
),
BottomNavigationBarItem(
label: s.chat,
icon: BlocBuilder<ChatsCubit, ChatsState>(
builder: (BuildContext context, ChatsState state) {
final hasUnreadMessages = state.chatsWithUnreadMessagesCount > 0;
return badges.Badge(
position: badges.BadgePosition.topEnd(),
badgeAnimation: const badges.BadgeAnimation.fade(),
badgeStyle: badges.BadgeStyle(
badgeColor: hasUnreadMessages ? Colors.red : Colors.transparent,
icon: BlocBuilder<ChatsCubit, ChatsState>(
builder: (BuildContext context, ChatsState state) {
final hasUnreadMessages = state.chatsWithUnreadMessagesCount > 0;
return badges.Badge(
position: badges.BadgePosition.topEnd(),
badgeAnimation: const badges.BadgeAnimation.fade(),
badgeStyle: badges.BadgeStyle(badgeColor: hasUnreadMessages ? Colors.red : Colors.transparent),
badgeContent: Text(
hasUnreadMessages ? state.chatsWithUnreadMessagesCount.toString() : '',
style: TextStyle(color: hasUnreadMessages ? Colors.white : Colors.transparent, fontSize: 8),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnChat,
width: 20,
height: 20,
colorFilter: pages![currentBottomNavBarIndex] == '/chat-select-screen'
? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
),
),
);
},
),
badgeContent: Text(
hasUnreadMessages ? state.chatsWithUnreadMessagesCount.toString() : '',
style: TextStyle(
color: hasUnreadMessages ? Colors.white : Colors.transparent,
fontSize: 8,
),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnChat,
width: 20,
height: 20,
colorFilter: pages![currentBottomNavBarIndex] == '/chat-select-screen'
? ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
)
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
),
),
);
},
),
),
BottomNavigationBarItem(
label: 'Уведомления',
icon: BlocBuilder<NotificationsCubit, NotificationsState>(
builder: (BuildContext context, NotificationsState state) {
return badges.Badge(
position: badges.BadgePosition.topEnd(),
badgeAnimation: const badges.BadgeAnimation.fade(),
showBadge: state is NotificationsStateLoaded && state.hasUnreadNotifications,
badgeContent: const Text(
'',
style: TextStyle(
color: Colors.white,
fontSize: 8,
),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnBell,
colorFilter: pages![currentBottomNavBarIndex] == '/notifications-screen'
? ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
)
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: 20,
height: 20,
),
),
);
},
),
),
BottomNavigationBarItem(
label: '',
icon: SvgPicture.asset(
ImageConstant.imgLogo,
height: 32,
icon: BlocBuilder<NotificationsCubit, NotificationsState>(
builder: (BuildContext context, NotificationsState state) {
return badges.Badge(
position: badges.BadgePosition.topEnd(),
badgeAnimation: const badges.BadgeAnimation.fade(),
showBadge: state is NotificationsStateLoaded && state.hasUnreadNotifications,
badgeContent: const Text('', style: TextStyle(color: Colors.white, fontSize: 8)),
child: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnBell,
colorFilter: pages![currentBottomNavBarIndex] == '/notifications-screen'
? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: 20,
height: 20,
),
),
);
},
),
),
BottomNavigationBarItem(label: '', icon: _buildLogoIcon()),
],
),
);
}
// Показываем child даже когда профиль еще загружается, чтобы экраны могли инициализироваться
return Scaffold(
body: widget.child,
);
return Scaffold(body: widget.child);
},
);
}

View File

@@ -1,5 +1,6 @@
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;
@@ -9,7 +10,8 @@ class CalendarRepository {
Future<List<CalendarModel>> getCalendar() async {
try {
return await calendarService.getCalendar();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,5 +1,6 @@
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;
@@ -10,7 +11,8 @@ class CancelPlanRepository {
}) async {
try {
await cancelService.cancelChosenPlan(id: id, body: body);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -12,6 +12,7 @@ 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;
@@ -60,6 +61,7 @@ class ChatRepository {
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
}
@@ -69,6 +71,7 @@ class ChatRepository {
return (await chatService.getChatRoom(uuid: uuid));
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
}
@@ -76,7 +79,8 @@ class ChatRepository {
Future<ChatRoomPaginationModel> getChatRooms({String? query, int page = 1}) async {
try {
return (await chatService.getChatRooms(query: query, page: page));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -84,7 +88,8 @@ class ChatRepository {
Future<ChatMessagesModel> getChatMessages(String id, int page) async {
try {
return (await chatService.getChatMessages(id: id, page: page));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -92,7 +97,8 @@ class ChatRepository {
Future<ChatRoomModel> getChatRoomIdByUserSlug({required String slug}) async {
try {
return (await chatService.getChatRoomIdByUserSlug(user: slug));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -100,7 +106,8 @@ class ChatRepository {
Future<void> markMessagesAsRead(String id, List<int> messageIds) async {
try {
await chatService.markMessagesAsRead(id: id, body: {'messages_ids': messageIds});
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -110,7 +117,8 @@ class ChatRepository {
}) async {
try {
await chatService.cancelChosenPlan(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -118,7 +126,8 @@ class ChatRepository {
Future<void> choseStartPlanDate({required int id, required Map<String, String> date}) async {
try {
await chatService.choseStartPlanDate(id: id, date: date);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -128,7 +137,8 @@ class ChatRepository {
}) async {
try {
return await chatService.getPaymentLink(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -138,7 +148,8 @@ class ChatRepository {
}) async {
try {
return await chatService.getChosenPlan(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -148,7 +159,8 @@ class ChatRepository {
}) async {
try {
await chatService.finishChosenPlan(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -159,7 +171,8 @@ class ChatRepository {
}) async {
try {
await chatService.complainAboutConsultation(id: id, body: body);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -172,7 +185,8 @@ class ChatRepository {
} else {
return response.data['id'];
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return 'error';
}
}

View File

@@ -1,5 +1,6 @@
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;
@@ -12,7 +13,8 @@ class ComplainingRepository {
}) async {
try {
await service.sendPlanComplain(slug: slug, category: category, body: body);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -24,7 +26,8 @@ class ComplainingRepository {
}) async {
try {
await service.sendRecipeComplain(slug: slug, category: category, body: body);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -36,7 +39,8 @@ class ComplainingRepository {
}) async {
try {
await service.sendBlogPostsComplain(id: id, category: category, body: body);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -48,7 +52,8 @@ class ComplainingRepository {
}) async {
try {
await service.sendProfileComplain(slug: slug, category: category, body: body);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -60,7 +65,8 @@ class ComplainingRepository {
}) async {
try {
await service.sendDietComplain(id: id, category: category, body: body);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -72,7 +78,8 @@ class ComplainingRepository {
}) async {
try {
await service.sendCommentComplain(id: id, category: category, body: body);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/consultant/become_consultant/models/consulta
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
import 'package:http_parser/http_parser.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class ConsultantRepository {
final ConsultantService consultantService;
@@ -18,7 +19,8 @@ class ConsultantRepository {
} else {
return await consultantService.consultantRegistration(consultantModel: consultant);
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -26,7 +28,8 @@ class ConsultantRepository {
Future<ConsultationRequestModel> getConsultantRequestStatus(String slug) async {
try {
return await consultantService.getConsultantRequestStatus(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -34,7 +37,8 @@ class ConsultantRepository {
Future<ConsultationRequestModel> getMyRequestStatus(String slug) async {
try {
return await consultantService.getMyRequestStatus(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -42,7 +46,8 @@ class ConsultantRepository {
Future<List<ConsultantCertPhotoModel>> getConsultantCertPhotos() async {
try {
return await consultantService.getConsultantCertPhotos();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -62,7 +67,8 @@ class ConsultantRepository {
}
return await consultantService.storeConsultantCertPhotos(photos: multipartFiles);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -70,7 +76,8 @@ class ConsultantRepository {
Future<void> hideConsultantCertPhoto(int id) async {
try {
await consultantService.hideConsultantCertPhoto(payload: {'cert_photo_id': id});
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -78,7 +85,8 @@ class ConsultantRepository {
Future<void> unHideConsultantCertPhoto(int id) async {
try {
await consultantService.unHideConsultantCertPhoto(payload: {'cert_photo_id': id});
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,6 +1,7 @@
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;
@@ -10,7 +11,8 @@ class ConsultantRatingRepository {
Future<StatisticsModel> getConsultantStatistics({String? date}) async {
try {
return await service.getConsultantStatistics(date: date);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -19,7 +21,8 @@ class ConsultantRatingRepository {
try {
final data = (await service.getScoreLogs(date: date)).data;
return data;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,6 +1,7 @@
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';
@@ -13,7 +14,8 @@ class ConsultationsRepository {
try {
final consultations = await consultationService.getConsultations();
return consultations.data;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -32,7 +34,8 @@ class ConsultationsRepository {
dateComparison: dateComparison,
);
return model.data;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -40,7 +43,8 @@ class ConsultationsRepository {
Future<StatisticModel> getStatistic() async {
try {
return await consultationService.getStatistics();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -48,7 +52,8 @@ class ConsultationsRepository {
Future<List<ConsultantFinanceModel>> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async {
try {
return await consultationService.getConsultantFinances(date, dateComparison);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -56,7 +61,8 @@ class ConsultationsRepository {
Future<List<UserPurchasesModel>> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async {
try {
return await consultationService.getUserPurchases(date, dateComparison);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,7 +1,6 @@
import 'dart:convert';
import 'dart:io';
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/const.dart';
@@ -16,6 +15,7 @@ import 'package:eatvio_app/features/profile/repositories/profile_repository.dart
import 'package:flutter_bloc/flutter_bloc.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<CreateDietState> {
@@ -347,10 +347,11 @@ class CreateDietCubit extends Cubit<CreateDietState> {
emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft));
return null;
} on DioException catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
} catch (e, st) {
await Sentry.captureException(e, stackTrace: st);
log(e.toString(), stackTrace: st);
emit(state.copyWith(isDietPublishing: false));
}
emit(state.copyWith(isDietPublishing: false));
return 'error';
}
@@ -381,6 +382,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
);
return null;
} catch (e, st) {
await Sentry.captureException(e, stackTrace: st);
log(e.toString(), stackTrace: st);
return 'error';
}

View File

@@ -2,6 +2,7 @@ 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;
@@ -18,7 +19,7 @@ class CreateDietRepository {
required String? details,
required File? image,
required List<List<List<DailySchedule>>>? dailySchedule,
}) {
}) async {
final dietMap = <String, dynamic>{};
if (dailySchedule != null) {
for (final (dayIndex, day) in dailySchedule.indexed) {
@@ -61,13 +62,18 @@ class CreateDietRepository {
dietMap.addAll({'name': name, 'duration': duration});
final formData = FormData.fromMap(dietMap);
switch (state) {
case DietState.active:
return createDietService.createPublishedDiet(formData: formData);
case DietState.draft:
return createDietService.createDraftedDiet(formData: formData);
case DietState.personal:
return createDietService.createPersonalDiet(formData: formData);
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;
}
}
@@ -94,7 +100,8 @@ class CreateDietRepository {
);
});
return dailySchedule;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -111,7 +118,7 @@ class CreateDietRepository {
String? details,
File? image,
List<List<List<DailySchedule>>>? dailySchedule,
}) {
}) async {
final dietMap = <String, dynamic>{};
if (dailySchedule != null) {
for (final (dayIndex, day) in dailySchedule.indexed) {
@@ -154,13 +161,18 @@ class CreateDietRepository {
}
final formData = FormData.fromMap(dietMap);
switch (state) {
case DietState.active:
return createDietService.updatePublished(diet: dietId, formData: formData);
case DietState.draft:
return createDietService.updateDrafted(diet: dietId, formData: formData);
case DietState.personal:
return createDietService.updatePersonal(diet: dietId, formData: formData);
try {
switch (state) {
case DietState.active:
return await createDietService.updatePublished(diet: dietId, formData: formData);
case DietState.draft:
return await createDietService.updateDrafted(diet: dietId, formData: formData);
case DietState.personal:
return await createDietService.updatePersonal(diet: dietId, formData: formData);
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
}

View File

@@ -341,7 +341,7 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
bool validateSelectedOptionDescription() {
final option = state.planData.options[state.selectedOptionIndex ?? 0];
final optionsValidationsStates = [...state.validationState.optionsValidationState];
if (option.description.length < 3) {
if (option.description == null || option.description!.length < 3) {
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(descriptionHasError: true);
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
return false;
@@ -356,7 +356,7 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
final optionsValidationsStates = [...state.validationState.optionsValidationState];
bool result = true;
if (options[0].description.length < 3) {
if (options[0].description == null || options[0].description!.length < 3) {
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(descriptionHasError: true);
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
result = false;
@@ -365,7 +365,7 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
}
if (state.selectedOptionIndex == null) return result;
if (options[1].description.length < 3) {
if (options[1].description == null || options[1].description!.length < 3) {
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(descriptionHasError: true);
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
result = false;
@@ -374,7 +374,7 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
}
if (options[2].description.length < 3) {
if (options[2].description == null || options[2].description!.length < 3) {
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(descriptionHasError: true);
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
result = false;

View File

@@ -94,7 +94,7 @@ class _OptionWidgetState extends State<OptionWidget> {
final price = double.tryParse(customerPriceController.text.replaceAll('₽', ''));
creatorPriceController.text = '${((price ?? 0) * 0.8).toStringAsFixed(2)}₽';
firstOptionTitleController.text = state.planData.options[selectedOptionIndex].short;
firstOptionDescriptionController.text = state.planData.options[selectedOptionIndex].description;
firstOptionDescriptionController.text = state.planData.options[selectedOptionIndex].description ?? '';
return MainBackgroundBouble(
padding: const EdgeInsets.all(16),
child: Column(

View File

@@ -1,5 +1,6 @@
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;
@@ -9,7 +10,13 @@ class CreatePlanRepository {
Future<void> createPlan({
required Map<String, dynamic> model,
}) async {
final formData = FormData.fromMap(model);
return await createPlanService.createPlan(formData: formData);
}
try {
final formData = FormData.fromMap(model);
return await createPlanService.createPlan(formData: formData);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
}

View File

@@ -11,6 +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:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
@@ -165,6 +166,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
await recipeRepository.publishRecipe(model, update, slug);
return true;
} catch (e, st) {
await Sentry.captureException(e, stackTrace: st);
log(e.toString(), stackTrace: st);
return false;
}
@@ -329,7 +331,8 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
try {
await recipeRepository.deleteRecipe(slug);
return true;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return false;
}
}

View File

@@ -6,6 +6,7 @@ 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,7 +17,8 @@ class RecipeRepository {
Future<Map<String, List<Product>>> inputProduct(String name) async {
try {
return await recipeService.getInputProduct(name);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -75,7 +77,8 @@ class RecipeRepository {
} else {
await recipeService.publishRecipe(formData: formData);
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -83,7 +86,8 @@ class RecipeRepository {
Future<RecipeModel> getRecipe(String recipeID) async {
try {
return await recipeService.getRecipe(recipeID);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -91,7 +95,8 @@ class RecipeRepository {
Future<List<RecipeTag>> getRecipesTags() async {
try {
return (await recipeService.getTags()).data;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -99,7 +104,8 @@ class RecipeRepository {
Future<void> deleteRecipe(String slug) async {
try {
return await recipeService.deleteRecipe(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -132,7 +132,12 @@ class _AddProductModalState extends State<AddProductModal> {
subtitle: glossariesList[i].grInPortion == 0
? null
: Text('${glossariesList[i].grInPortion} г', style: TextStyle(height: 1, color: Colors.grey.shade600)),
trailing: Icon(Icons.add_rounded, color: Colors.grey.shade400),
trailing: Icon(
productNameController.text.trim().toLowerCase() == glossariesList[i].name.toLowerCase()
? Icons.radio_button_checked
: Icons.radio_button_off,
color: Colors.blueAccent,
),
contentPadding: const EdgeInsets.only(left: 12),
);
},
@@ -166,7 +171,9 @@ class _AddProductModalState extends State<AddProductModal> {
InkWell(
onTap: () {
// Автоматически выбираем первый элемент, если текст точно совпадает
if (selectedGlossary == null && glossariesList.isNotEmpty && productNameController.text.trim().toLowerCase() == glossariesList[0].name.toLowerCase()) {
if (selectedGlossary == null &&
glossariesList.isNotEmpty &&
productNameController.text.trim().toLowerCase() == glossariesList[0].name.toLowerCase()) {
selectedGlossary = glossariesList[0];
}

View File

@@ -9,6 +9,7 @@ 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;
@@ -69,67 +70,83 @@ class DiaryRepository {
return updatedDiaryDay;
}
return diaryDay;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<List<DiaryDayModel>> getDiaryIndexByDate(String date) async {
return (await diaryService.getDiaryIndexByDate(date: date));
try {
return (await diaryService.getDiaryIndexByDate(date: date));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<DiaryDayModel> getDiaryDay(int diaryId) async {
// Align with getDiaryByDate: enrich portions with glossary nutritional info
final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId);
final DiaryDayModel diaryDay = diaryWrapper.diary;
Set<int> ids = {};
for (var meal in diaryDay.meals) {
for (var portion in meal.portions) {
if (portion.productNutritionalValueId != null) {
ids.add(portion.productNutritionalValueId!);
try {
// Align with getDiaryByDate: enrich portions with glossary nutritional info
final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId);
final DiaryDayModel diaryDay = diaryWrapper.diary;
Set<int> 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<MealModel> updatedMeals = List.from(diaryDay.meals);
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
final meal = updatedMeals[mealIndex];
final updatedPortions = meal.portions.map((portion) {
if (portion.productNutritionalValueId != null) {
try {
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
return portion.copyWith(
productCaloriesInfo: ProductCaloriesInfoModel(
id: glossaryItem.id,
productName: glossaryItem.name,
calories: glossaryItem.energyOn100Gr,
proteins: glossaryItem.proteinsOn100Gr,
fats: glossaryItem.fatsOn100Gr,
carbohydrates: glossaryItem.carbohydratesOn100Gr,
energyOnPortion: glossaryItem.energyOnPortion,
proteinsOnPortion: glossaryItem.proteinsOnPortion,
fatsOnPortion: glossaryItem.fatsOnPortion,
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
),
);
} catch (e) {
return portion;
if (ids.isNotEmpty) {
final glossary = await getGlossariesIndex(ids: ids.toList());
final List<MealModel> updatedMeals = List.from(diaryDay.meals);
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
final meal = updatedMeals[mealIndex];
final updatedPortions = meal.portions.map((portion) {
if (portion.productNutritionalValueId != null) {
try {
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
return portion.copyWith(
productCaloriesInfo: ProductCaloriesInfoModel(
id: glossaryItem.id,
productName: glossaryItem.name,
calories: glossaryItem.energyOn100Gr,
proteins: glossaryItem.proteinsOn100Gr,
fats: glossaryItem.fatsOn100Gr,
carbohydrates: glossaryItem.carbohydratesOn100Gr,
energyOnPortion: glossaryItem.energyOnPortion,
proteinsOnPortion: glossaryItem.proteinsOnPortion,
fatsOnPortion: glossaryItem.fatsOnPortion,
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
),
);
} catch (e) {
return portion;
}
}
}
return portion;
}).toList();
return portion;
}).toList();
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
}
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
return updatedDiaryDay;
}
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
return updatedDiaryDay;
return diaryDay;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
return diaryDay;
}
Future<NutritionDataModel> getNutritionData(int index) async {
return await diaryService.getNutrition(diary: index);
try {
return await diaryService.getNutrition(diary: index);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> addPortionToMeal({
@@ -140,14 +157,19 @@ class DiaryRepository {
required int quantity,
int? productNutritionalValueId,
}) async {
return await diaryService.addProductToMeal(
diary: diaryId,
typeNo: mealId,
productName: productName,
portion: portion,
quantity: quantity,
productNutritionalValueId: productNutritionalValueId,
);
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;
}
}
Future<void> updateProductInMeal({
@@ -160,26 +182,51 @@ class DiaryRepository {
int? productNutritionalValueId,
required PortionState portionState,
}) async {
Map<String, Object?> 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);
try {
Map<String, Object?> 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;
}
}
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
return await diaryService.markEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
try {
return await diaryService.markEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
return await diaryService.unmarkEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
try {
return await diaryService.unmarkEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
return await diaryService.restore(diary: diaryId, typeNo: mealId, portionModel: portionId);
try {
return await diaryService.restore(diary: diaryId, typeNo: mealId, portionModel: portionId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
return await diaryService.deleteProductFromMeal(diary: diaryId, typeNo: mealId, portionModel: portionId);
try {
return await diaryService.deleteProductFromMeal(diary: diaryId, typeNo: mealId, portionModel: portionId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> uploadPhotos({required int diaryId, required int mealId, required List<File> files}) async {
@@ -187,7 +234,8 @@ class DiaryRepository {
for (int i = 0; i < files.length; i++) {
await diaryService.uploadPhotoOfMeal(diary: diaryId, typeNo: mealId, photo: files[i]);
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -195,16 +243,22 @@ class DiaryRepository {
Future<void> deletePhoto({required int diaryId, required int mealId, required DiaryPhotoModel photo}) async {
try {
await diaryService.deletePhotoOfMeal(diaryId: diaryId, typeNo: mealId, userAttachment: photo.id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async {
return await diaryService.finishDiaryDay(
diary: diaryId,
finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment),
);
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;
}
}
Future<GlossaryModerationModel> sendProductToModeration({
@@ -214,15 +268,21 @@ class DiaryRepository {
required double fats,
required double carbohydrates,
}) async {
return await diaryService.sendProductToModeration(
data: ProductModerationModel(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates).toJson(),
);
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;
}
}
Future<void> fillSatiety({required int diaryId, required int mealId, required int satiety}) async {
try {
return await diaryService.fillSatiety(diary: diaryId, typeNo: mealId, satiety: satiety);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -230,7 +290,8 @@ class DiaryRepository {
Future<void> fillMood({required int diaryId, required int mealId, required int mood}) async {
try {
return await diaryService.fillMood(diary: diaryId, typeNo: mealId, mood: mood);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -238,7 +299,8 @@ class DiaryRepository {
Future<void> fillTime({required int diaryId, required int mealId, required String time}) async {
try {
return await diaryService.fillTime(diary: diaryId, typeNo: mealId, time: time);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -246,7 +308,8 @@ class DiaryRepository {
Future<void> fillWakeup({required int diaryId, required String time}) async {
try {
return await diaryService.fillWakeup(diary: diaryId, wakeUp: time);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -254,7 +317,8 @@ class DiaryRepository {
Future<void> fillRetire({required int diaryId, required String time}) async {
try {
return await diaryService.fillRetire(diary: diaryId, retire: time);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -263,7 +327,8 @@ class DiaryRepository {
try {
await diaryService.commentUserDay(id: diaryId, body: commentModel);
_diaryForceFetchController.add('force');
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -271,7 +336,8 @@ class DiaryRepository {
Future<ConsultantReviewModel> getDiaryComments({required int planId, required int diaryId}) async {
try {
return await diaryService.getDiaryComments(planId: planId, diaryId: diaryId);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -279,7 +345,8 @@ class DiaryRepository {
Future<void> viewCommentOfDiaryDayByConsultant({required int diaryId}) async {
try {
await diaryService.viewCommentOfDiaryDayByConsultant(diaryId: diaryId);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -293,7 +360,8 @@ class DiaryRepository {
}
try {
return await diaryService.getGlossariesIndex(filters: filters);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -302,7 +370,8 @@ class DiaryRepository {
try {
final diaryDay = await diaryService.getDiaryDayV2(day: day);
return setCPFCToDiary(diaryDay);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -311,7 +380,8 @@ class DiaryRepository {
try {
await diaryService.createDay(body: CreateDayModel(day: day));
await getDiaryByDate(day);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -4,6 +4,7 @@ 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});
@@ -19,7 +20,8 @@ class DietRepository {
Future<DietLikesModel> likeDiet(int id) async {
try {
return await dietsService.likeDiet(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -28,7 +30,8 @@ class DietRepository {
try {
final diet = await dietsService.getDiet(id: id);
return diet;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -37,7 +40,8 @@ class DietRepository {
try {
final diet = await dietsService.getDietBySlug(slug: slug);
return diet;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -45,7 +49,8 @@ class DietRepository {
Future<Diet> addToFavorite(int id) async {
try {
return await dietsService.addToFavorite(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -53,7 +58,8 @@ class DietRepository {
Future<Diet> removeFromFavorite(int id) async {
try {
return await dietsService.removeFromFavorite(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -61,7 +67,8 @@ class DietRepository {
Future<void> startDiet(int id, String date) async {
try {
return await dietsService.startDiet(id: id, body: {'date': date});
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -69,7 +76,8 @@ class DietRepository {
Future<void> cancelDiet(int id) async{
try {
return await dietsService.cancelDiet(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -66,7 +66,7 @@ abstract class PacketOptions with _$PacketOptions {
@JsonKey(name: "items", fromJson: _parseItems) required List<String> items,
@JsonKey(name: "price") required dynamic price,
@JsonKey(name: "short") required String short,
@JsonKey(name: "description_md") required String description,
@JsonKey(name: "description_md") String? description,
@JsonKey(name: "has_personal_diet", fromJson: _parseHasPersonalDietField) required bool hasPersonalDiet,
}) = _PacketOptions;

View File

@@ -72,7 +72,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
final consultantsScrollController = ScrollController();
@override
void initState() {
chipsLabelList = hidePlans ? ['Все', 'Рецепты', 'Диеты', 'Консультанты'] : ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
chipsLabelList = hidePlans ? ['Все', 'Консультанты', 'Рецепты', 'Диеты'] : ['Все', 'Планы питания', 'Консультанты', 'Рецепты', 'Диеты'];
consultantsScrollController.addListener(_onScrollConsultants);
_checkRegistrationDate(context);
// Инициализация глобальных кубитов при первом открытии экрана
@@ -115,7 +115,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
Widget build(BuildContext context) {
FlutterNativeSplash.remove();
return Scaffold(
floatingActionButton: _selectedChipIndex == 1
floatingActionButton: ((_selectedChipIndex == 2 && hidePlans) || (_selectedChipIndex == 3 && !hidePlans))
? FloatingActionButton.small(
heroTag: 'recipesTagsFilter',
backgroundColor: appTheme.blueA400,
@@ -137,11 +137,13 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
body: NestedScrollView(
controller: switch (_selectedChipIndex) {
0 => blogPostsScrollController,
1 => recipesScrollController,
2 => dietsScrollController,
3 when !hidePlans => plansScrollController,
3 when hidePlans => consultantsScrollController,
4 when !hidePlans => consultantsScrollController,
1 when !hidePlans => plansScrollController,
1 when hidePlans => consultantsScrollController,
2 when !hidePlans => consultantsScrollController,
2 when hidePlans => recipesScrollController,
3 when !hidePlans => recipesScrollController,
3 when hidePlans => dietsScrollController,
4 when !hidePlans => dietsScrollController,
_ => blogPostsScrollController,
},
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
@@ -173,11 +175,13 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
children: [
CustomSearchBar(
searchController: switch (_selectedChipIndex) {
1 => recipesController,
2 => dietsController,
3 when !hidePlans => plansController,
3 when hidePlans => consultantsController,
4 when !hidePlans => consultantsController,
1 when !hidePlans => plansController,
1 when hidePlans => consultantsController,
2 when !hidePlans => consultantsController,
2 when hidePlans => recipesController,
3 when !hidePlans => recipesController,
3 when hidePlans => dietsController,
4 when !hidePlans => dietsController,
_ => postsController,
},
onChanged: (query) {
@@ -187,18 +191,20 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
case 0:
context.read<FeedCubit>().loadFeedBlogPostList(query: query);
break;
case 1:
context.read<RecipesCubit>().loadRecipesList(query: query);
break;
case 2:
context.read<DietsCubit>().loadDiets(query: query);
break;
case 3 when !hidePlans:
case 1 when !hidePlans:
context.read<PlansCubit>().loadPlans(query: query);
break;
case 1 when hidePlans:
case 2 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
break;
case 2 when hidePlans:
case 3 when !hidePlans:
context.read<RecipesCubit>().loadRecipesList(query: query);
break;
case 3 when hidePlans:
case 4 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
context.read<DietsCubit>().loadDiets(query: query);
break;
}
});
@@ -208,18 +214,20 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
case 0:
context.read<FeedCubit>().loadFeedBlogPostList();
break;
case 1:
context.read<RecipesCubit>().loadRecipesList();
break;
case 2:
context.read<DietsCubit>().loadDiets();
break;
case 3 when !hidePlans:
case 1 when !hidePlans:
context.read<PlansCubit>().loadPlans();
break;
case 1 when hidePlans:
case 2 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
break;
case 2 when hidePlans:
case 3 when !hidePlans:
context.read<RecipesCubit>().loadRecipesList();
break;
case 3 when hidePlans:
case 4 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
context.read<DietsCubit>().loadDiets();
break;
}
});
@@ -228,18 +236,28 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
),
const SizedBox(width: 12),
switch (_selectedChipIndex) {
1 => AddItemButton(
1 when !hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
),
2 when hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name) : null,
),
2 => AddItemButton(
3 when !hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name) : null,
),
3 when hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false
? () async {
await GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'isEditing': false});
}
: null,
),
3 when !hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
4 when !hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false
? () async {
await GoRouter.of(context).pushNamed(CreateDietScreen.name);
}
: null,
),
_ => const AddItemButton(key: AppKeys.feedScreenBlogPostCreationButton),
},
@@ -252,11 +270,13 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
},
body: switch (_selectedChipIndex) {
0 => BlogPosts(handleVisibility: context.read<ViewsCubit>().handleVisibility),
1 => Recipes(handleVisibility: context.read<ViewsCubit>().handleVisibility),
2 => Diets(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when !hidePlans => Plans(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when hidePlans => const Consultants(),
4 when !hidePlans => const Consultants(),
1 when !hidePlans => Plans(handleVisibility: context.read<ViewsCubit>().handleVisibility),
1 when hidePlans => const Consultants(),
2 when !hidePlans => const Consultants(),
2 when hidePlans => Recipes(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when !hidePlans => Recipes(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when hidePlans => Diets(handleVisibility: context.read<ViewsCubit>().handleVisibility),
4 when !hidePlans => Diets(handleVisibility: context.read<ViewsCubit>().handleVisibility),
_ => Center(child: Text(chipsLabelList[_selectedChipIndex])),
},
),

View File

@@ -7,6 +7,7 @@ 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;
@@ -16,7 +17,8 @@ class FeedRepository {
Future<FeedBlogPostSettingsModel> getFeedBlogPosts({String? query, int page = 1}) async {
try {
return (await feedService.getBlogPostList(page: page, query: query));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -24,7 +26,8 @@ class FeedRepository {
Future<FeedBlogPostModel> getBlogPost({required int id}) async {
try {
return await feedService.getBlogPost(postId: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -32,7 +35,8 @@ class FeedRepository {
Future<List<Recipe>> getRecipes({String? query, List<int>? tags, int page = 1}) async {
try {
return (await feedService.getRecipes(query: query, tags: tags, page: page)).recipesList;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -40,7 +44,8 @@ class FeedRepository {
Future<DietsPagination> getDiets({int page = 1, String? query, String? types, int? duration, String? sorting}) async {
try {
return (await feedService.getDiets(page: page, query: query, types: types, duration: duration, sorting: sorting));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -48,7 +53,8 @@ class FeedRepository {
Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async {
try {
return (await feedService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -56,7 +62,8 @@ class FeedRepository {
Future<CommentModel> getBlogPostComments({required String id}) async {
try {
return (await feedService.getBlogPostComments(id: id));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -64,7 +71,8 @@ class FeedRepository {
Future<void> sendBlogPostComment({required String id, required String commentText, int? parentCommentId}) async {
try {
await feedService.sendBlogPostComment(id: id, body: commentText, parentCommentId: parentCommentId);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -72,7 +80,8 @@ class FeedRepository {
Future<CommentModel> getDietsComments({required String id}) async {
try {
return (await feedService.getDietsComments(id: id));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -80,7 +89,8 @@ class FeedRepository {
Future<void> sendDietsComment({required String id, required String commentText, int? parentCommentId}) async {
try {
await feedService.sendDietsComment(id: id, body: commentText, parentCommentId: parentCommentId);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -88,7 +98,8 @@ class FeedRepository {
Future<CommentModel> getRecipesComments({required String id}) async {
try {
return (await feedService.getRecipesComments(id: id));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -96,7 +107,8 @@ class FeedRepository {
Future<void> sendRecipesComment({required String id, required String commentText, int? parentCommentId}) async {
try {
await feedService.sendRecipesComment(id: id, body: commentText, parentCommentId: parentCommentId);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -104,7 +116,8 @@ class FeedRepository {
Future<CommentModel> getPlansComments({required String slug}) async {
try {
return (await feedService.getPlansComments(slug: slug));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -112,7 +125,8 @@ class FeedRepository {
Future<void> sendPlansComment({required String slug, required String commentText, int? parentCommentId}) async {
try {
await feedService.sendPlansComment(slug: slug, body: commentText, parentCommentId: parentCommentId);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -120,7 +134,8 @@ class FeedRepository {
Future<void> likeBlogPost({required int id}) async {
try {
await feedService.likeBlogPost(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -128,7 +143,8 @@ class FeedRepository {
Future<void> unlikedBlogPost({required int id}) async {
try {
await feedService.unlikedBlogPost(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -136,7 +152,8 @@ class FeedRepository {
Future<List<FeedConsultantModel>> getConsultants([String? name]) async {
try {
return (await feedService.getConsultants(name: name)).data;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -144,7 +161,8 @@ class FeedRepository {
Future<void> viewPost(int id) async {
try {
return (await feedService.viewPost(id: id));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -152,7 +170,8 @@ class FeedRepository {
Future<void> viewRecipe(String slug) async {
try {
return (await feedService.viewRecipe(slug: slug));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -160,7 +179,8 @@ class FeedRepository {
Future<void> viewDiet(int id) async {
try {
return (await feedService.viewDiet(id: id));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -168,7 +188,8 @@ class FeedRepository {
Future<void> viewPlan(String slug) async {
try {
return (await feedService.viewPlan(slug: slug));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -176,7 +197,8 @@ class FeedRepository {
Future<void> viewContent({required ViewModel viewModel}) async {
try {
await feedService.viewContent(viewModel: viewModel);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,5 +1,6 @@
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;
@@ -11,7 +12,8 @@ class GlossaryRepository {
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
try {
return (await glossaryService.getGlossaryByQuery(query: query)).glossariesList;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,5 +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/const.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/token_storage.dart';
@@ -7,6 +8,7 @@ import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
@@ -141,7 +143,10 @@ class MenuScreen extends StatelessWidget {
onPressed: () async {
try {
await sl<Dio>().post('${sl<AppConfig>().baseUrl}/api/profile/logout');
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
} finally {
await sl<WsManager>().clear();
await TokenStorage.removeTokenInStorage();
if (context.mounted) {
GoRouter.of(context).go('/auth-screen', extra: true);

View File

@@ -1,7 +1,7 @@
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;
@@ -15,6 +15,7 @@ class BlockedUsersRepository {
return (await blockedUserService.getBlockedUsers()).profilesList;
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
}
@@ -28,6 +29,7 @@ class BlockedUsersRepository {
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
}
@@ -41,6 +43,7 @@ class BlockedUsersRepository {
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
}

View File

@@ -1,5 +1,6 @@
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;
@@ -7,10 +8,20 @@ class NotificationSettingsRepository {
NotificationSettingsRepository(this._notificationSettingsService);
Future<NotificationSettingsModel> getNotificationSettings() async {
return await _notificationSettingsService.getNotificationSettings();
try {
return await _notificationSettingsService.getNotificationSettings();
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<NotificationSettingsModel> updateNotificationSettings(Map<String, dynamic> data) async {
return await _notificationSettingsService.updateNotificationSettings(data: data);
try {
return await _notificationSettingsService.updateNotificationSettings(data: data);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
}

View File

@@ -142,8 +142,7 @@ class MyDietsScreenContent extends StatelessWidget {
}
final diets = switch (state.selectedStateIndex) {
1 => state.draftDiets,
2 => state.personalDiets,
1 => state.personalDiets,
_ => state.activeDiets,
};

View File

@@ -169,8 +169,35 @@ class _MyDietWidgetState extends State<MyDietWidget> {
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
// Верхний ряд: Удалить + Редактировать (или + Поделиться для опубликованных)
Row(
children: [
// Кнопка удаления (слева)
CustomOutlinedButton(
onPressed: () async {
final result = await context.read<MyDietsCubit>().deleteDiet(widget.diet.id);
if (result) {
if (!context.mounted) return;
showBottomSheetWithBlur(
context: context,
child: DietDeletedBottomSheet(title: widget.diet.name),
);
await context.read<MyDietsCubit>().loadAllDiets();
}
},
height: 52,
width: 52,
leftIcon: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 20),
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red),
buttonStyle: ButtonStyle(
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
side: const WidgetStatePropertyAll(BorderSide.none),
),
text: '',
),
const SizedBox(width: 6),
// Кнопка редактирования (центр)
Expanded(
child: CustomOutlinedButton(
onPressed: () async {
@@ -182,20 +209,17 @@ class _MyDietWidgetState extends State<MyDietWidget> {
_editDiet(widget.diet);
}
},
text: switch (state.selectedStateIndex) {
1 => s.publish,
2 => s.toOffer,
_ => s.edit,
},
text: s.edit,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black),
buttonStyle: ButtonStyle(
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
side: const WidgetStatePropertyAll(BorderSide.none),
),
),
),
// Кнопка поделиться (справа) для опубликованных
if (state.selectedStateIndex == 0) ...[
const SizedBox(width: 6),
CustomOutlinedButton(

View File

@@ -21,7 +21,6 @@ class _MyDietsTabState extends State<MyDietsTab> {
final s = S.of(context);
if (tabTitles.isEmpty) {
tabTitles.add(s.published);
tabTitles.add(s.drafted);
tabTitles.add(s.personal);
}
super.didChangeDependencies();
@@ -31,37 +30,29 @@ class _MyDietsTabState extends State<MyDietsTab> {
Widget build(BuildContext context) {
return BlocBuilder<MyDietsCubit, MyDietsState>(
builder: (context, state) {
return Align(
alignment: Alignment.centerLeft,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Container(
height: 48,
decoration: BoxDecoration(
color: appTheme.whiteA700,
borderRadius: BorderRadius.circular(100),
),
padding: const EdgeInsets.all(4),
child: Row(
children: [
Container(
width: 484,
height: 48,
decoration: BoxDecoration(
color: appTheme.whiteA700,
borderRadius: BorderRadius.circular(100),
),
padding: const EdgeInsets.all(4),
child: Row(
children: [
...List.generate(
3,
(index) => Expanded(
child: AnimatedOptionButton(
textStyle: TextStyles.textSMedium,
isSelected: state.selectedStateIndex == index,
text: tabTitles[index],
onTap: () {
widget.additionalOnTap?.call();
context.read<MyDietsCubit>().changeSelectedStateIndex(index);
},
),
),
),
],
...List.generate(
2,
(index) => Expanded(
child: AnimatedOptionButton(
textStyle: TextStyles.textSMedium,
isSelected: state.selectedStateIndex == index,
text: tabTitles[index],
onTap: () {
widget.additionalOnTap?.call();
context.read<MyDietsCubit>().changeSelectedStateIndex(index);
},
),
),
),
],

View File

@@ -1,5 +1,6 @@
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;
@@ -9,7 +10,8 @@ class MyDietsRepository {
Future<void> deleteDiet(int dietId) async {
try {
await myDietsService.deleteDiet(dietId: dietId);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -31,7 +33,8 @@ class MyDietsRepository {
sorting: sorting,
state: state,
));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,6 +1,7 @@
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;
@@ -8,7 +9,8 @@ class CardsRepository {
Future<PaymentUrlModel> addCard() async {
try {
return await cardsService.addCard();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -16,7 +18,8 @@ class CardsRepository {
Future<List<CardModel>> getCards() async {
try {
return await cardsService.getCards();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -24,7 +27,8 @@ class CardsRepository {
Future<void> markAsDefault({required int id}) async {
try {
await cardsService.markAsDefault(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -32,7 +36,8 @@ class CardsRepository {
Future<void> deleteCard({required int id}) async {
try {
await cardsService.deleteCard(id: id);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -40,7 +45,8 @@ class CardsRepository {
Future<void> collectCharge({required int chosenPlaId}) async {
try {
await cardsService.collectCharge(chosenPlanId: chosenPlaId);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,5 +1,8 @@
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;
@@ -9,34 +12,28 @@ class MyPlansRepository {
Future<void> deletePlan(String slug) async {
try {
await myPlansService.deletePlan(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<PlanPaginationModel> getPlans({
int page = 1,
String? query,
String? types,
int? duration,
int? minPrice,
int? maxPrice,
}) async {
Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async {
try {
return (await myPlansService.getPlans(
page: page,
query: query,
types: types,
duration: duration,
minPrice: minPrice,
maxPrice: maxPrice,
));
} catch (e) {
return (await myPlansService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
} catch (e, st) {
log(e.toString(), error: e, stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
}
Future<void> unpublish(String slug) async {
return await myPlansService.unpublish(slug: slug);
try {
return await myPlansService.unpublish(slug: slug);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
}

View File

@@ -1,15 +1,17 @@
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<Map<String, dynamic>?> getNotifications({required int page}) {
Future<Map<String, dynamic>?> getNotifications({required int page}) async {
try {
return notificationsService.getNotifications(page: page);
} catch (e) {
return await notificationsService.getNotifications(page: page);
} catch (e, s) {
log(e.toString());
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -17,7 +19,8 @@ class NotificationsRepository {
Future<void> markNotificationsAsRead() async {
try {
await notificationsService.markNotificationsAsRead();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -41,27 +41,38 @@ class PersonalDietSuggestionScreenContent extends StatelessWidget {
),
body: state.isLoading
? const Center(child: Loader())
: SingleChildScrollView(
child: Column(
children: [
...List.generate(state.consultationsWithDiet.length, (index) {
return ConsultationWidget(
consultationModel: state.consultationsWithDiet[index],
dietName: state.dietName,
);
}),
const SizedBox(
height: 12,
: (state.consultationsWithDiet.isEmpty && state.consultationsWithoutDiet.isEmpty)
? Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Text(
'Нет активных консультаций с персональной диетой',
style: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
textAlign: TextAlign.center,
),
),
...List.generate(state.consultationsWithoutDiet.length, (index) {
return ConsultationWidget(
consultationModel: state.consultationsWithoutDiet[index],
dietName: state.dietName,
);
})
],
),
),
)
: SingleChildScrollView(
child: Column(
children: [
...List.generate(state.consultationsWithDiet.length, (index) {
return ConsultationWidget(
consultationModel: state.consultationsWithDiet[index],
dietName: state.dietName,
);
}),
const SizedBox(
height: 12,
),
...List.generate(state.consultationsWithoutDiet.length, (index) {
return ConsultationWidget(
consultationModel: state.consultationsWithoutDiet[index],
dietName: state.dietName,
);
})
],
),
),
);
},
);

View File

@@ -1,5 +1,6 @@
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;
@@ -9,23 +10,26 @@ class PersonalDietSuggestionRepository {
Future<List<ConsultationModel>> getConsultationWithPersonalDiets() async{
try {
return (await personalDietSuggestionService.getConsultationWithPersonalDiets()).data;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> attachPersonalDiet({required int consultationId, required int dietId}) {
Future<void> attachPersonalDiet({required int consultationId, required int dietId}) async {
try {
return personalDietSuggestionService.attachPersonalDiet(consultationId: consultationId, dietId: dietId);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> reattachPersonalDiet({required int consultationId, required int dietId}) {
Future<void> reattachPersonalDiet({required int consultationId, required int dietId}) async {
try {
return personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId);
} catch (e) {
return await personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -19,7 +20,8 @@ class PlanRepository {
Future<PlanModel> getPlan(String slug) async {
try {
return planService.getPlan(slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -27,7 +29,8 @@ class PlanRepository {
Future<void> likePlan(String slug) async {
try {
await planService.likePlan(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -35,7 +38,8 @@ class PlanRepository {
Future<void> addToFavorite(String slug) async {
try {
await planService.addToFavorite(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -43,7 +47,8 @@ class PlanRepository {
Future<void> removeFromFavorite(String slug) async {
try {
await planService.removeFromFavorite(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -51,7 +56,8 @@ class PlanRepository {
Future<ChosenPlanModel> chosePlan(String slug, Map<String, dynamic> option ) async {
try {
return await planService.chosePlan(slug: slug, option: option);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,5 +1,6 @@
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;
@@ -10,7 +11,8 @@ class PlansRepository {
try {
final plans = (await plansService.getPlansBySlug(author: slug)).data;
return plans.where((plan) => plan.state == 'active').toList();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -9,6 +9,7 @@ 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;
@@ -18,7 +19,8 @@ class ProfileRepository {
Future<ProfileModel> getProfileBySlug(String slug) async {
try {
return await profileService.getProfileBySlug(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -26,7 +28,8 @@ class ProfileRepository {
Future<ProfileDetailsModel> getProfileDetailsBySlug(String slug) async {
try {
return await profileService.getProfileDetailsBySlug(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -34,7 +37,8 @@ class ProfileRepository {
Future<ProfileModel> getMyProfile() async {
try {
return await profileService.getMyProfile();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -42,7 +46,8 @@ class ProfileRepository {
Future<ProfileDetailsModel> getMyProfileDetails() async {
try {
return await profileService.getMyProfileDetails();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -54,7 +59,8 @@ class ProfileRepository {
waistCircumference: waistCircumference,
hipCircumference: hipCircumference,
));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -62,7 +68,8 @@ class ProfileRepository {
Future<void> addWeight({required double weight}) async {
try {
return (await profileService.addWeight(weight: weight));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -70,7 +77,8 @@ class ProfileRepository {
Future<List<FeedBlogPostModel>> getBlogPostsByAuthorSlug({required String slug}) async {
try {
return (await profileService.getBlogPostByAuthorSlug(slug: slug));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -82,7 +90,8 @@ class ProfileRepository {
} else {
return (await profileService.getWeight(period: period));
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -94,7 +103,8 @@ class ProfileRepository {
} else {
return (await profileService.getCircumferences(period: period));
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -102,7 +112,8 @@ class ProfileRepository {
Future<List<SubModel>> getSubscribersBySlug(String slug, [String? query]) async {
try {
return (await profileService.getSubscribers(slug: slug, query: query)).data;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -110,7 +121,8 @@ class ProfileRepository {
Future<List<SubModel>> getSubscriptionsBySlug(String slug, [String? query]) async {
try {
return (await profileService.getSubscriptions(slug: slug, query: query)).data;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -118,7 +130,8 @@ class ProfileRepository {
Future<void> subscribe(String slug) async {
try {
return await profileService.subscribe(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -126,7 +139,8 @@ class ProfileRepository {
Future<void> unsubscribe(String slug) async {
try {
return await profileService.unsubscribe(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -134,7 +148,8 @@ class ProfileRepository {
Future<void> uploadPhoto(File file) async {
try {
return await profileService.uploadPhoto(photo: file);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -142,7 +157,8 @@ class ProfileRepository {
Future<void> deletePhoto() async {
try {
return await profileService.deletePhoto();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -150,26 +166,42 @@ class ProfileRepository {
Future<List<ChosenDietModel>> getChosenDiets() async {
try {
return (await profileService.getChosenDiets()).diets;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<String> getPassKey({required String routeName, Map<String, dynamic>? routeParams}) async {
return (await profileService.getPassKey(
json: PassKeyParams(routeName: routeName, routeParams: routeParams),
)).data['key_is']!;
try {
return (await profileService.getPassKey(
json: PassKeyParams(routeName: routeName, routeParams: routeParams),
)).data['key_is']!;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<String> getMyPhone() async {
return (jsonDecode(await profileService.getMyPhone())['phone']);
try {
return (jsonDecode(await profileService.getMyPhone())['phone']);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
Future<void> sendEmailCode({String? newEmail}) async {
if (newEmail != null) {
return await profileService.sendEmailCode(data: {'new_email': newEmail});
} else {
return await profileService.sendEmailCode();
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;
}
}
@@ -177,7 +209,8 @@ class ProfileRepository {
try {
await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}");
return true;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return false;
}
}
@@ -185,7 +218,8 @@ class ProfileRepository {
Future<void> deleteAccount() async {
try {
await profileService.deleteAccount();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -18,7 +19,8 @@ class AboutRecipeRepository {
Future<Recipe> getRecipe(String recipeID) async {
try {
return await recipeService.getRecipe(recipeID);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -26,7 +28,8 @@ class AboutRecipeRepository {
Future<void> likeRecipe(String slug) async {
try {
await recipeService.likeRecipe(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -34,7 +37,8 @@ class AboutRecipeRepository {
Future<void> addToFavorite(String slug) async {
try {
await recipeService.addToFavorite(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -42,7 +46,8 @@ class AboutRecipeRepository {
Future<void> removeFromFavorite(String slug) async {
try {
await recipeService.removeFromFavorite(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -50,7 +55,8 @@ class AboutRecipeRepository {
Future<void> deleteRecipe(String slug) async {
try {
await recipeService.deleteRecipe(slug: slug);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,5 +1,6 @@
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;
@@ -9,7 +10,8 @@ class MyRecipesRepository {
Future<List<MyRecipeModel>> getMyRecipes() async {
try {
return (await myRecipesService.getMyRecipes()).data;
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -4,6 +4,7 @@ import 'package:eatvio_app/features/registration/models/user_registration_model.
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';
class RegistrationRepository {
final RegistrationService registrationService;
@@ -37,7 +38,7 @@ class RegistrationRepository {
throw 'Ошибка регистрации: $response';
}
}
} on DioException catch (e) {
} on DioException catch (e, s) {
if (e.response != null && e.response?.statusCode == 422) {
final data = e.response?.data;
String errorMsg = '';
@@ -62,6 +63,10 @@ class RegistrationRepository {
throw 'Ошибка регистрации: $data';
}
}
await Sentry.captureException(e, stackTrace: s);
rethrow;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -69,7 +74,8 @@ class RegistrationRepository {
Future<LocationModel> getLocationsListByQuery(String query) async {
try {
return (await registrationService.getLocationsByAddress(query: query));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -77,7 +83,8 @@ class RegistrationRepository {
Future<LocationModel> getLocationsListByCoordinates(String latitude, String longitude) async {
try {
return (await registrationService.getLocationsByCoordinate(latitude: latitude, longitude: longitude));
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -90,7 +97,8 @@ class RegistrationRepository {
} else {
return false;
}
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return false;
}
}

View File

@@ -3,6 +3,7 @@ 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';
@@ -180,7 +181,7 @@ class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
firstName: state.firstName,
lastName: state.lastName,
email: state.email,
phone: state.phone,
phone: ValidationUtils.normalizePhone(state.phone),
gender: state.gender,
password: password,
confirmPassword: password,

View File

@@ -21,6 +21,7 @@ class FirstStepContent extends StatefulWidget {
class _FirstStepContentState extends State<FirstStepContent> {
final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy);
final maskFormatter = MaskTextInputFormatter(mask: '+7 (###) ### ## ##', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy);
final firstNameController = TextEditingController();
@@ -37,7 +38,10 @@ class _FirstStepContentState extends State<FirstStepContent> {
firstNameController.text = context.read<RegistrationCubitV2>().state.firstName;
lastNameController.text = context.read<RegistrationCubitV2>().state.lastName;
emailController.text = context.read<RegistrationCubitV2>().state.email;
phoneController.text = context.read<RegistrationCubitV2>().state.phone;
final phone = context.read<RegistrationCubitV2>().state.phone;
if (phone.isNotEmpty) {
phoneController.text = maskFormatter.maskText(phone);
}
dateController.text = context.read<RegistrationCubitV2>().state.birthday?.format('dd.MM.yyyy') ?? '';
}
@@ -113,6 +117,7 @@ class _FirstStepContentState extends State<FirstStepContent> {
hintText: '+7 (999) 999 99 99',
controller: phoneController,
hasError: !state.isPhoneValid,
inputFormatters: [maskFormatter],
keyboardType: TextInputType.phone,
onChanged: (val) {
context.read<RegistrationCubitV2>().setPhone(val);

View File

@@ -1,5 +1,6 @@
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;
@@ -9,7 +10,8 @@ class RegistrationRepositoryV2 {
try {
final response = await registrationService.register(model: registrationModel.toJson());
return response['token'];
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -12,7 +13,8 @@ class SettingsRepository {
Future<ProfileSettingsModel> getProfileSettings() async {
try {
return await settingsService.getProfileSettings();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -20,7 +22,8 @@ class SettingsRepository {
Future<ProfileSettingsModel> updateProfileSettings({required ProfileSettingsModel profileSettingsModel}) async {
try {
return await settingsService.updateProfileSettings(data: profileSettingsModel.toJson());
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -28,7 +31,8 @@ class SettingsRepository {
Future<ProfileDetailsModel> updateProfileDetails({required ProfileDetailsModel profileDetailsModel}) async {
try {
return await settingsService.updateProfileDetails(data: profileDetailsModel.toJson());
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -36,7 +40,8 @@ class SettingsRepository {
Future<OAuthAccountsResponseModel> getOAuthAccounts() async {
try {
return await settingsService.getOAuthAccounts();
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -44,7 +49,8 @@ class SettingsRepository {
Future<void> linkOAuthAccount({required OAuthAttachRequest request}) async {
try {
return await settingsService.linkOAuthAccount(request);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}
@@ -52,7 +58,8 @@ class SettingsRepository {
Future<void> unlinkOAuthAccount({required String provider}) async {
try {
return await settingsService.unlinkOAuthAccount(provider);
} catch (e) {
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
}

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.3.13+94
version: 1.3.17+97
publish_to: 'none'
environment:
@@ -36,7 +36,6 @@ dependencies:
url_launcher: ^6.3.2
easy_debounce: ^2.0.3
badges: ^3.1.2
google_fonts: ^6.3.3
syncfusion_flutter_charts: ^32.1.20
syncfusion_flutter_gauges: ^32.1.20
table_calendar: ^3.2.0
@@ -84,6 +83,7 @@ dependencies:
gpassword: ^1.0.0
webview_flutter_platform_interface: ^2.14.0
intl_utils: ^2.8.13
sentry_dio: ^9.10.0
dev_dependencies:
build_runner: ^2.10.4
@@ -111,6 +111,50 @@ flutter:
- assets/icons/
- assets/splash/
- assets/lottie_animations/
fonts:
- family: Mulish-Local
fonts:
- asset: assets/fonts/Mulish-ExtraLight.ttf
weight: 200
- asset: assets/fonts/Mulish-ExtraLightItalic.ttf
weight: 200
style: italic
- asset: assets/fonts/Mulish-Light.ttf
weight: 300
- asset: assets/fonts/Mulish-LightItalic.ttf
weight: 300
style: italic
- asset: assets/fonts/Mulish-Regular.ttf
weight: 400
- asset: assets/fonts/Mulish-Italic.ttf
weight: 400
style: italic
- asset: assets/fonts/Mulish-Medium.ttf
weight: 500
- asset: assets/fonts/Mulish-MediumItalic.ttf
weight: 500
style: italic
- asset: assets/fonts/Mulish-SemiBold.ttf
weight: 600
- asset: assets/fonts/Mulish-SemiBoldItalic.ttf
weight: 600
style: italic
- asset: assets/fonts/Mulish-Bold.ttf
weight: 700
- asset: assets/fonts/Mulish-BoldItalic.ttf
weight: 700
style: italic
- asset: assets/fonts/Mulish-ExtraBold.ttf
weight: 800
- asset: assets/fonts/Mulish-ExtraBoldItalic.ttf
weight: 800
style: italic
- asset: assets/fonts/Mulish-Black.ttf
weight: 900
- asset: assets/fonts/Mulish-BlackItalic.ttf
weight: 900
style: italic
flutter_intl:
enabled: true

View File

@@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -13,6 +15,8 @@ class MockErrorInterceptorHandler extends Mock implements ErrorInterceptorHandle
class MockResponse extends Mock implements Response {}
class MockWsManager extends Mock implements WsManager {}
class MockDioException extends Mock implements DioException {}
class FakeRequestOptions extends Fake implements RequestOptions {}
@@ -31,6 +35,13 @@ void main() {
setUp(() async {
SharedPreferences.setMockInitialValues({});
final mockWsManager = MockWsManager();
when(() => mockWsManager.clear()).thenAnswer((_) async {});
await sl.reset(); // Сбрасываем sl перед каждым тестом
sl.registerSingleton<WsManager>(mockWsManager);
interceptor = TokenInterceptor();
// Сбрасываем токен перед каждым тестом
try {

View File

@@ -30,6 +30,28 @@ void main() {
});
});
group('normalizePhone', () {
test('normalizes phone starting with +7', () {
expect(ValidationUtils.normalizePhone('+7 (915) 062 61 51'), '+79150626151');
});
test('normalizes phone starting with 8', () {
expect(ValidationUtils.normalizePhone('89150626151'), '+79150626151');
});
test('normalizes 10-digit phone', () {
expect(ValidationUtils.normalizePhone('9150626151'), '+79150626151');
});
test('returns empty string for empty input', () {
expect(ValidationUtils.normalizePhone(''), '');
});
test('returns empty string for input with no digits', () {
expect(ValidationUtils.normalizePhone('abc'), '');
});
});
group('isValidUrl', () {
test('returns true for valid URL', () {
expect(ValidationUtils.isValidUrl('https://example.com'), isTrue);

View File

@@ -326,7 +326,7 @@ void main() {
expect(find.byType(DietTypeChoiceWidget), findsOneWidget);
expect(find.byType(DietImagePickerWidget), findsOneWidget);
// Action buttons
expect(find.byType(CustomOutlinedButton), findsNWidgets(3));
expect(find.byType(CustomOutlinedButton), findsNWidgets(2));
});
});
}

View File

@@ -106,7 +106,7 @@ void main() {
// Assert
expect(find.byType(MyDietsTab), findsOneWidget);
expect(find.byType(AnimatedOptionButton), findsNWidgets(3));
expect(find.byType(AnimatedOptionButton), findsNWidgets(2));
});
testWidgets('should handle tab selection in MyDietsTab', (WidgetTester tester) async {
@@ -119,7 +119,7 @@ void main() {
// Act - Find and tap the second tab
final tabButtons = find.byType(AnimatedOptionButton);
expect(tabButtons, findsNWidgets(3));
expect(tabButtons, findsNWidgets(2));
// Tap the second tab (index 1)
await tester.tap(tabButtons.at(1));
@@ -221,7 +221,7 @@ void main() {
// Assert
expect(find.byType(MyDietsTab), findsOneWidget);
expect(find.byType(AnimatedOptionButton), findsNWidgets(3));
expect(find.byType(AnimatedOptionButton), findsNWidgets(2));
});
testWidgets('should handle MyDietsTab additionalOnTap callback', (WidgetTester tester) async {

Binary file not shown.

File diff suppressed because one or more lines are too long