diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index fd291367..2aeef03a 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -119,7 +119,7 @@ jobs: run: flutter test - name: Build apk - run: flutter build apk --release + run: flutter build apk --release --dart-define=HIDE_PLANS=false - name: Rename and move apk run: | diff --git a/.gitignore b/.gitignore index bbb74115..b448cede 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ *.mocks.dart android/build/ ios/XCBuildData/ +.env +env.example diff --git a/ENV_SETUP.md b/ENV_SETUP.md new file mode 100644 index 00000000..0873fb1c --- /dev/null +++ b/ENV_SETUP.md @@ -0,0 +1,84 @@ +# Настройка переменных окружения + +Этот проект использует переменные окружения для хранения секретных данных и конфигурации. + +## Установка + +1. Скопируйте файл `env.example` в `.env`: +```bash +cp env.example .env +``` + +2. Отредактируйте файл `.env` и замените значения на ваши собственные секреты. + +## Переменные окружения + +### API URLs +- `BASE_URL` - основной URL API (по умолчанию: https://eatvio.ru) +- `DEV_BASE_URL` - URL API для разработки (по умолчанию: https://dev2.eatvio.com) + +### Sentry Configuration +- `SENTRY_DSN` - DSN для Sentry мониторинга + +### Firebase Configuration +- `FIREBASE_PROJECT_ID` - ID проекта Firebase +- `FIREBASE_DATABASE_URL` - URL базы данных Firebase +- `FIREBASE_STORAGE_BUCKET` - bucket для хранения файлов +- `FIREBASE_MESSAGING_SENDER_ID` - ID отправителя сообщений + +### Firebase Android Configuration +- `FIREBASE_ANDROID_API_KEY` - API ключ для Android +- `FIREBASE_ANDROID_APP_ID` - ID приложения для Android + +### Firebase iOS Configuration +- `FIREBASE_IOS_API_KEY` - API ключ для iOS +- `FIREBASE_IOS_APP_ID` - ID приложения для iOS +- `FIREBASE_IOS_BUNDLE_ID` - Bundle ID для iOS + +### App Store URLs +- `APP_STORE_URL` - URL App Store +- `RUSTORE_URL` - URL RuStore + +### Support URLs +- `TELEGRAM_SUPPORT_URL` - URL поддержки в Telegram +- `SUPPORT_EMAIL` - email для поддержки + +### Test Credentials (только для разработки) +- `TEST_EMAIL` - тестовый email +- `TEST_PASSWORD` - тестовый пароль + +### Email Validation API +- `EMAIL_VALIDATION_API_URL` - URL API для валидации email + +### Connection Check URLs +- `GOOGLE_CHECK_URL` - URL для проверки соединения (Google) +- `YANDEX_CHECK_URL` - URL для проверки соединения (Yandex) + +## Безопасность + +⚠️ **ВАЖНО**: Файл `.env` добавлен в `.gitignore` и не должен попадать в репозиторий. Все секретные данные должны храниться только в этом файле. + +## Использование в коде + +Для доступа к переменным окружения используйте класс `EnvConfig`: + +```dart +import 'package:eatvio_app/core/env_config.dart'; + +String baseUrl = EnvConfig.baseUrl; +String sentryDsn = EnvConfig.sentryDsn; +``` + +## Миграция + +Все секреты были перенесены из кода в переменные окружения: + +1. **Sentry DSN** - из `main.dart` +2. **Firebase конфигурация** - из `firebase_options.dart` +3. **API URLs** - из `const.dart` +4. **URL для проверки соединения** - из `internet_connection_checker_screen.dart` +5. **URL для обновления приложения** - из `update_screen.dart` +6. **URL поддержки Telegram** - из `support_screen.dart` +7. **API для валидации email** - из `registration_repository.dart` +8. **Тестовые учетные данные** - из `test_cases.dart` +9. **Email поддержки** - из `support_screen.dart` \ No newline at end of file diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 9340117d..7dff33a3 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -20,7 +20,13 @@ platform :ios do lane :beta do match(type: "appstore", readonly: true) increment_build_number(xcodeproj: "Runner.xcodeproj") - build_app(workspace: "Runner.xcworkspace", scheme: "Runner", export_method: "app-store", codesigning_identity: "Apple Distribution") + build_app( + workspace: "Runner.xcworkspace", + scheme: "Runner", + export_method: "app-store", + codesigning_identity: "Apple Distribution", + build_args: "--dart-define=HIDE_PLANS=true" + ) app_store_connect_api_key(key_id: "325JS9JCZH", issuer_id: "dfa27d1d-2bb8-4d4d-847e-0b32f9b880b3", key_filepath: "./AuthKey.p8") upload_to_testflight end diff --git a/lib/core/const.dart b/lib/core/const.dart index f22f79aa..efed7d49 100644 --- a/lib/core/const.dart +++ b/lib/core/const.dart @@ -1,5 +1,7 @@ -String baseUrl = 'https://eatvio.ru'; -bool hidePlans = true; +import 'package:eatvio_app/core/env_config.dart'; + +String baseUrl = EnvConfig.baseUrl; +bool hidePlans = const bool.fromEnvironment('HIDE_PLANS', defaultValue: false); const List durationValues = [7, 14, 21]; diff --git a/lib/core/env_config.dart b/lib/core/env_config.dart new file mode 100644 index 00000000..23c18f62 --- /dev/null +++ b/lib/core/env_config.dart @@ -0,0 +1,43 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class EnvConfig { + static String get baseUrl => dotenv.env['BASE_URL'] ?? 'https://eatvio.ru'; + static String get devBaseUrl => dotenv.env['DEV_BASE_URL'] ?? 'https://dev2.eatvio.com'; + + // Sentry Configuration + static String get sentryDsn => dotenv.env['SENTRY_DSN'] ?? 'http://ba7f2653067776f3034e6e89850e85a1@sentry.md-desk.ru:9000/2'; + + // Firebase Configuration + static String get firebaseProjectId => dotenv.env['FIREBASE_PROJECT_ID'] ?? 'eatvio-3467b'; + static String get firebaseDatabaseUrl => dotenv.env['FIREBASE_DATABASE_URL'] ?? 'https://eatvio-3467b-default-rtdb.europe-west1.firebasedatabase.app'; + static String get firebaseStorageBucket => dotenv.env['FIREBASE_STORAGE_BUCKET'] ?? 'eatvio-3467b.firebasestorage.app'; + static String get firebaseMessagingSenderId => dotenv.env['FIREBASE_MESSAGING_SENDER_ID'] ?? '613980912922'; + + // Firebase Android Configuration + static String get firebaseAndroidApiKey => dotenv.env['FIREBASE_ANDROID_API_KEY'] ?? 'AIzaSyBUe1UqAAMfgMcd4hWdRoLF1Os-yn-HE9k'; + static String get firebaseAndroidAppId => dotenv.env['FIREBASE_ANDROID_APP_ID'] ?? '1:613980912922:android:4de95e5f2d50ba69ecc0a3'; + + // Firebase iOS Configuration + static String get firebaseIosApiKey => dotenv.env['FIREBASE_IOS_API_KEY'] ?? 'AIzaSyBnE8tQjL9_1HuXrTnqNy6qbXpoenO36yQ'; + static String get firebaseIosAppId => dotenv.env['FIREBASE_IOS_APP_ID'] ?? '1:613980912922:ios:c557d140a1212e63ecc0a3'; + static String get firebaseIosBundleId => dotenv.env['FIREBASE_IOS_BUNDLE_ID'] ?? 'com.eatvioapp.app'; + + // App Store URLs + static String get appStoreUrl => dotenv.env['APP_STORE_URL'] ?? 'https://apps.apple.com/app/idYOUR_APP_ID'; + static String get rustoreUrl => dotenv.env['RUSTORE_URL'] ?? 'https://www.rustore.ru/catalog/app/com.eatvioapp.app'; + + // Telegram Support + static String get telegramSupportUrl => dotenv.env['TELEGRAM_SUPPORT_URL'] ?? 'https://t.me/eatvio'; + static String get supportEmail => dotenv.env['SUPPORT_EMAIL'] ?? 'help@eatv.io'; + + // Test Credentials (for development only) + static String get testEmail => dotenv.env['TEST_EMAIL'] ?? 'reshetnick.mike+user1@gmail.com'; + static String get testPassword => dotenv.env['TEST_PASSWORD'] ?? '1234567890'; + + // Email Validation API + static String get emailValidationApiUrl => dotenv.env['EMAIL_VALIDATION_API_URL'] ?? 'https://api.validate.email/validate'; + + // Connection Check URLs + static String get googleCheckUrl => dotenv.env['GOOGLE_CHECK_URL'] ?? 'https://google.com'; + static String get yandexCheckUrl => dotenv.env['YANDEX_CHECK_URL'] ?? 'https://yandex.ru'; +} diff --git a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart index 04a0b7fb..760a8783 100644 --- a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart +++ b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart @@ -14,10 +14,7 @@ import 'package:go_router/go_router.dart'; class ConsultationCardWidget extends StatefulWidget { final ConsultationModel consultationsModel; - const ConsultationCardWidget({ - super.key, - required this.consultationsModel, - }); + const ConsultationCardWidget({super.key, required this.consultationsModel}); @override State createState() => _ConsultationCardWidgetState(); @@ -32,13 +29,7 @@ class _ConsultationCardWidgetState extends State { 'Дневник заполнен, прокомментирован консультантом и после этого клиент просмотрел комментарии.', ]; - final List legendColors = const [ - Colors.white, - Color(0xFFF0C497), - Color(0xFFFF9500), - Color(0xFF2F80ED), - Color(0xFF4CD964), - ]; + final List legendColors = const [Colors.white, Color(0xFFF0C497), Color(0xFFFF9500), Color(0xFF2F80ED), Color(0xFF4CD964)]; late final List<(int, DiaryModel)> givenDaysWithDayNumber; final List> legendStates = [ @@ -46,7 +37,7 @@ class _ConsultationCardWidgetState extends State { [DiaryStateEnum.started, DiaryStateEnum.lastDayStarted], [DiaryStateEnum.finished, DiaryStateEnum.lastDayFinished], [DiaryStateEnum.commented, DiaryStateEnum.lastDayCommented], - [DiaryStateEnum.reviewedByUser] + [DiaryStateEnum.reviewedByUser], ]; @override void initState() { @@ -72,32 +63,19 @@ class _ConsultationCardWidgetState extends State { children: [ Row( children: [ - CircleAvatar( - radius: 30, - backgroundImage: NetworkImage(baseUrl + widget.consultationsModel.userProfilePhotoUrl), - ), + CircleAvatar(radius: 30, backgroundImage: NetworkImage(baseUrl + widget.consultationsModel.userProfilePhotoUrl)), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.consultationsModel.userName, - style: const TextStyle( - height: 1, - color: Color(0xFF323F4B), - fontSize: 16, - fontWeight: FontWeight.w600, - ), + style: const TextStyle(height: 1, color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w600), ), const SizedBox(height: 4), Text( widget.consultationsModel.planTitle, - style: const TextStyle( - height: 1, - color: Color(0xFF88949F), - fontSize: 14, - fontWeight: FontWeight.w500, - ), + style: const TextStyle(height: 1, color: Color(0xFF88949F), fontSize: 14, fontWeight: FontWeight.w500), ), ], ), @@ -127,12 +105,7 @@ class _ConsultationCardWidgetState extends State { children: [ Text( 'День ${widget.consultationsModel.currentDiaryDay} из ${widget.consultationsModel.duration}', - style: const TextStyle( - height: 1, - color: Color(0xFF323F4B), - fontSize: 16, - fontWeight: FontWeight.w500, - ), + style: const TextStyle(height: 1, color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500), ), GestureDetector( onTap: () { @@ -149,12 +122,7 @@ class _ConsultationCardWidgetState extends State { children: [ const Text( 'Легенда', - style: TextStyle( - height: 1, - fontSize: 20, - fontWeight: FontWeight.w700, - color: Color(0xFF323F4B), - ), + style: TextStyle(height: 1, fontSize: 20, fontWeight: FontWeight.w700, color: Color(0xFF323F4B)), ), const SizedBox(height: 16), ListView.separated( @@ -170,11 +138,7 @@ class _ConsultationCardWidgetState extends State { ), ); }, - child: const Icon( - Icons.help_outline_rounded, - color: Color(0xFF323F4B), - size: 20, - ), + child: const Icon(Icons.help_outline_rounded, color: Color(0xFF323F4B), size: 20), ), ], ), @@ -185,7 +149,8 @@ class _ConsultationCardWidgetState extends State { children: List.generate(widget.consultationsModel.duration, (index) { final day = givenDaysWithDayNumber.firstWhereOrNull((e) => e.$1 == index); final hasActivity = day != null && day.$2.state != DiaryStateEnum.init; - final canConsultantOpenThisDay = day != null && + final canConsultantOpenThisDay = + day != null && (day.$2.state != DiaryStateEnum.init && day.$2.state != DiaryStateEnum.started && day.$2.state != DiaryStateEnum.lastDayStarted); return Padding( padding: const EdgeInsets.only(right: 12), @@ -195,81 +160,74 @@ class _ConsultationCardWidgetState extends State { InkWell( onTap: () { if (!hasActivity || !canConsultantOpenThisDay) { - showErrorDialog(context, - title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}'); + showErrorDialog( + context, + title: s.you_can_not_open_this_day, + description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}', + ); return; } - GoRouter.of(context).push(UserDiaryScreen.path, extra: { - 'diaryId': day.$2.id, - 'currentDiaryDay': index + 1, - }); + GoRouter.of(context).push(UserDiaryScreen.path, extra: {'diaryId': day.$2.id, 'currentDiaryDay': index + 1}); }, borderRadius: BorderRadius.circular(100), child: Ink( decoration: BoxDecoration( - color: hasActivity ? appTheme.dividerColor : appTheme.dividerColor.withValues(alpha: 0.4), - borderRadius: BorderRadius.circular(100)), + color: hasActivity ? appTheme.dividerColor : appTheme.dividerColor.withValues(alpha: 0.4), + borderRadius: BorderRadius.circular(100), + ), width: 58, height: 32, child: Center( child: Text( (index + 1).toString(), - style: TextStyles.textMSemibold.copyWith( - color: hasActivity ? appTheme.gray : appTheme.gray.withValues(alpha: 0.4), - ), + style: TextStyles.textMSemibold.copyWith(color: hasActivity ? appTheme.gray : appTheme.gray.withValues(alpha: 0.4)), ), ), ), ), if (hasActivity) ...[ - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), switch (day.$2.state) { DiaryStateEnum.init => const SizedBox.shrink(), DiaryStateEnum.started => Container( - width: 8, - height: 8, - decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]), - ), + width: 8, + height: 8, + decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]), + ), DiaryStateEnum.lastDayStarted => Container( - width: 8, - height: 8, - decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]), - ), + width: 8, + height: 8, + decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]), + ), DiaryStateEnum.finished => Container( - width: 8, - height: 8, - decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]), - ), + width: 8, + height: 8, + decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]), + ), DiaryStateEnum.lastDayFinished => Container( - width: 8, - height: 8, - decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]), - ), + width: 8, + height: 8, + decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]), + ), DiaryStateEnum.commented => Container( - width: 8, - height: 8, - decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]), - ), + width: 8, + height: 8, + decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]), + ), DiaryStateEnum.lastDayCommented => Container( - width: 8, - height: 8, - decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]), - ), + width: 8, + height: 8, + decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]), + ), DiaryStateEnum.reviewedByUser => Container( - width: 8, - height: 8, - decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[4]), - ), + width: 8, + height: 8, + decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[4]), + ), }, - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), ] else - const SizedBox( - height: 24, - ), + const SizedBox(height: 24), ], ), ); @@ -280,11 +238,7 @@ class _ConsultationCardWidgetState extends State { const SizedBox(height: 12), Text( '${widget.consultationsModel.consultationOptionPrice.toStringAsFixed(0)} ₽', - style: const TextStyle( - color: Color(0xFF019A3E), - fontSize: 20, - fontWeight: FontWeight.w600, - ), + style: const TextStyle(color: Color(0xFF019A3E), fontSize: 20, fontWeight: FontWeight.w600), ), const SizedBox(height: 16), Row( @@ -293,35 +247,26 @@ class _ConsultationCardWidgetState extends State { borderRadius: BorderRadius.circular(20), onTap: () { final lastActiveDay = givenDaysWithDayNumber - .where( - (e) => e.$2.state == DiaryStateEnum.finished || e.$2.state == DiaryStateEnum.lastDayFinished, - ) + .where((e) => e.$2.state == DiaryStateEnum.finished || e.$2.state == DiaryStateEnum.lastDayFinished) .lastOrNull; if (lastActiveDay != null) { - GoRouter.of(context).push(UserDiaryScreen.path, extra: { - 'diaryId': lastActiveDay.$2.id, - 'currentDiaryDay': lastActiveDay.$1, - }); + GoRouter.of(context).push(UserDiaryScreen.path, extra: {'diaryId': lastActiveDay.$2.id, 'currentDiaryDay': lastActiveDay.$1}); return; } - showErrorDialog(context, - title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}'); + showErrorDialog( + context, + title: s.you_can_not_open_this_day, + description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}', + ); }, child: Ink( width: MediaQuery.of(context).size.width - 32 - 52 - 8, padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: appTheme.blueA400, - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400), child: const Center( child: Text( 'Комментировать отчёт', - style: TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w600, - ), + style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600), ), ), ), @@ -333,16 +278,8 @@ class _ConsultationCardWidgetState extends State { child: Ink( height: 52, width: 52, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: const Color(0xFFF6F6F6), - ), - child: const Center( - child: Icon( - Icons.chat_outlined, - size: 24, - ), - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFF6F6F6)), + child: const Center(child: Icon(Icons.chat_outlined, size: 24)), ), ), ], @@ -360,21 +297,13 @@ class _ConsultationCardWidgetState extends State { Container( height: 20, width: 20, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: color, - ), + decoration: BoxDecoration(shape: BoxShape.circle, color: color), ), const SizedBox(width: 12), Flexible( child: Text( text, - style: const TextStyle( - height: 1, - fontSize: 16, - fontWeight: FontWeight.w500, - color: Color(0xFF323F4B), - ), + style: const TextStyle(height: 1, fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF323F4B)), ), ), ], diff --git a/lib/features/dev_tools/presentation/dev_tools_screen.dart b/lib/features/dev_tools/presentation/dev_tools_screen.dart index a4ff7730..4922a87d 100644 --- a/lib/features/dev_tools/presentation/dev_tools_screen.dart +++ b/lib/features/dev_tools/presentation/dev_tools_screen.dart @@ -9,6 +9,7 @@ import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/features/dev_tools/test_cases.dart'; +import 'package:eatvio_app/core/env_config.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -97,10 +98,15 @@ class _DevToolsScreenContentState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: Column( children: [ - Padding(padding: const EdgeInsets.all(8.0), child: Row(children: [Text('BaseUrl: $baseUrlForTest')])), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row(children: [Text('BaseUrl: $baseUrlForTest')]), + ), Row( children: [ - Expanded(child: CustomTextFormField(controller: controller, hintText: 'type new base url')), + Expanded( + child: CustomTextFormField(controller: controller, hintText: 'type new base url'), + ), IconButton( onPressed: () { setState(() { @@ -116,13 +122,13 @@ class _DevToolsScreenContentState extends State { ), const SizedBox(height: 10), CustomOutlinedButton( - text: baseUrl == 'https://dev2.eatvio.com' ? 'Set production base url' : 'Set dev2 base url', + text: baseUrl == EnvConfig.devBaseUrl ? 'Set production base url' : 'Set dev2 base url', onPressed: () { setState(() { - if (baseUrl == 'https://eatvio.ru') { - baseUrlForTest = 'https://dev2.eatvio.com'; + if (baseUrl == EnvConfig.baseUrl) { + baseUrlForTest = EnvConfig.devBaseUrl; } else { - baseUrlForTest = 'https://eatvio.ru'; + baseUrlForTest = EnvConfig.baseUrl; } baseUrl = baseUrlForTest.toLowerCase(); sl().options.baseUrl = baseUrl; @@ -132,7 +138,7 @@ class _DevToolsScreenContentState extends State { buttonStyle: const ButtonStyle(), ), SizedBox(height: baseUrlForTest != baseUrl ? 40 : 10), - if (baseUrl.toLowerCase() != 'https://eatvio.ru') + if (baseUrl.toLowerCase() != EnvConfig.baseUrl.toLowerCase()) ListView.builder( shrinkWrap: true, itemCount: testCases.length, @@ -175,12 +181,7 @@ class _DevToolsScreenContentState extends State { builder: (context) => AlertDialog( title: const Text('Test Failed'), content: SingleChildScrollView(child: Text(result.error ?? 'No error details.')), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('OK'))], ), ); } @@ -197,12 +198,7 @@ class _DevToolsScreenContentState extends State { builder: (context) => AlertDialog( title: const Text('Test Failed'), content: SingleChildScrollView(child: Text('$e\n$stack')), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('OK'))], ), ); } @@ -216,10 +212,10 @@ class _DevToolsScreenContentState extends State { TestState.fail => Container(width: 20, height: 20, color: Colors.red), TestState.notRun => Container(width: 20, height: 20, color: Colors.grey), TestState.loading => const SizedBox( - width: 20, - height: 20, - child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())), - ), + width: 20, + height: 20, + child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())), + ), }, ], ), diff --git a/lib/features/dev_tools/test_cases.dart b/lib/features/dev_tools/test_cases.dart index eb72a1e7..3d43bd2a 100644 --- a/lib/features/dev_tools/test_cases.dart +++ b/lib/features/dev_tools/test_cases.dart @@ -35,6 +35,7 @@ import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart'; import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart'; import 'package:eatvio_app/features/registration/services/registration_service.dart'; import 'package:eatvio_app/features/settings/services/settings_service.dart'; +import 'package:eatvio_app/core/env_config.dart'; class TestResult { final bool success; @@ -477,7 +478,9 @@ class AuthServiceTest implements TestCases { final dio = sl(); final authService = AuthService(dio, baseUrl: baseUrl); // Use dummy credentials, expecting failure but no crash - await authService.getTokenByCredentials(credentials: const CredentialsModel(email: 'reshetnick.mike+user1@gmail.com', password: '1234567890')); + await authService.getTokenByCredentials( + credentials: CredentialsModel(email: EnvConfig.testEmail, password: EnvConfig.testPassword), + ); return TestResult(false, 'Should not succeed'); } catch (e) { return TestResult(true, 'Exception expected for dummy credentials'); diff --git a/lib/features/internet_connection_checker/internet_connection_checker_screen.dart b/lib/features/internet_connection_checker/internet_connection_checker_screen.dart index 3aa1bc34..576eb891 100644 --- a/lib/features/internet_connection_checker/internet_connection_checker_screen.dart +++ b/lib/features/internet_connection_checker/internet_connection_checker_screen.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_svg/svg.dart'; import 'package:http/http.dart' as http; +import 'package:eatvio_app/core/env_config.dart'; class InternetConnectionCheckerScreen extends StatefulWidget { const InternetConnectionCheckerScreen({super.key, required this.child}); @@ -66,12 +67,7 @@ class _InternetConnectionCheckerScreenState extends State emailIsReachable(String email) async { try { - final response = await sl().getUri(Uri.parse('https://api.validate.email/validate?email=$email')); + final response = await sl().getUri(Uri.parse('${EnvConfig.emailValidationApiUrl}?email=$email')); if (response.data['result']['reachable'] != 'invalid') { return true; } else { diff --git a/lib/features/support/presentation/pages/support_screen.dart b/lib/features/support/presentation/pages/support_screen.dart index b550040f..c6f7719a 100644 --- a/lib/features/support/presentation/pages/support_screen.dart +++ b/lib/features/support/presentation/pages/support_screen.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:eatvio_app/core/env_config.dart'; class SupportScreen extends StatelessWidget { const SupportScreen({super.key}); @@ -15,48 +16,28 @@ class SupportScreen extends StatelessWidget { final s = S.of(context); return Scaffold( - appBar: AppBar( - title: const Text('Поддержка'), - ), + appBar: AppBar(title: const Text('Поддержка')), body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.all(16), - child: Text( - 'Если у вас есть вопросы по работе нашего приложения, вы можете задать их, перейдя по ссылке:', - style: TextStyle( - fontSize: 16, - ), - ), + child: Text('Если у вас есть вопросы по работе нашего приложения, вы можете задать их, перейдя по ссылке:', style: TextStyle(fontSize: 16)), ), InkWell( onTap: () { - launchUrl( - Uri.parse('https://t.me/eatvio'), - mode: LaunchMode.externalApplication, - ); + launchUrl(Uri.parse(EnvConfig.telegramSupportUrl), mode: LaunchMode.externalApplication); }, borderRadius: BorderRadius.circular(16), child: Ink( width: double.infinity, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(16)), padding: const EdgeInsets.all(16), child: Row( children: [ - SvgPicture.asset( - 'assets/icons/telegram.svg', - colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn), - ), + SvgPicture.asset('assets/icons/telegram.svg', colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn)), const SizedBox(width: 12), - const Text( - 'Telegram: t.me/eatvio', - style: TextStyle( - fontSize: 16, - ), - ), + Text('Telegram: ${EnvConfig.telegramSupportUrl.replaceFirst('https://', '')}', style: const TextStyle(fontSize: 16)), ], ), ), @@ -64,26 +45,19 @@ class SupportScreen extends StatelessWidget { const SizedBox(height: 8), InkWell( onTap: () { - Clipboard.setData(const ClipboardData(text: 'help@eatv.io')); + Clipboard.setData(ClipboardData(text: EnvConfig.supportEmail)); showInfoToast(s.mail_was_copied); }, borderRadius: BorderRadius.circular(16), child: Ink( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(16)), width: double.infinity, padding: const EdgeInsets.all(16), - child: const Row( + child: Row( children: [ - Icon(Icons.alternate_email_rounded), - SizedBox(width: 12), - Text( - 'E-mail: help@eatv.io', - style: TextStyle( - fontSize: 16, - ), - ), + const Icon(Icons.alternate_email_rounded), + const SizedBox(width: 12), + Text('E-mail: ${EnvConfig.supportEmail}', style: const TextStyle(fontSize: 16)), ], ), ), @@ -92,13 +66,7 @@ class SupportScreen extends StatelessWidget { const SizedBox(height: 16), const Padding( padding: EdgeInsets.symmetric(horizontal: 16), - child: Text( - 'Правовые документы', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), + child: Text('Правовые документы', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)), ), const SizedBox(height: 12), InkWell( @@ -106,12 +74,7 @@ class SupportScreen extends StatelessWidget { borderRadius: BorderRadius.circular(16), child: const Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), - child: Text( - 'Публичная офёрта', - style: TextStyle( - fontSize: 16, - ), - ), + child: Text('Публичная офёрта', style: TextStyle(fontSize: 16)), ), ), const SizedBox(height: 8), @@ -120,12 +83,7 @@ class SupportScreen extends StatelessWidget { borderRadius: BorderRadius.circular(16), child: const Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), - child: Text( - 'Политика конфиденциальности', - style: TextStyle( - fontSize: 16, - ), - ), + child: Text('Политика конфиденциальности', style: TextStyle(fontSize: 16)), ), ), const SizedBox(height: 8), @@ -134,12 +92,7 @@ class SupportScreen extends StatelessWidget { borderRadius: BorderRadius.circular(16), child: const Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), - child: Text( - 'О компании', - style: TextStyle( - fontSize: 16, - ), - ), + child: Text('О компании', style: TextStyle(fontSize: 16)), ), ), ], diff --git a/lib/features/update_screen/presentation/update_screen.dart b/lib/features/update_screen/presentation/update_screen.dart index 186987c2..68ec9640 100644 --- a/lib/features/update_screen/presentation/update_screen.dart +++ b/lib/features/update_screen/presentation/update_screen.dart @@ -6,6 +6,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../../core/theme/theme_helper.dart'; +import '../../../../core/env_config.dart'; class UpdateScreen extends StatelessWidget { const UpdateScreen({super.key}); @@ -17,16 +18,7 @@ class UpdateScreen extends StatelessWidget { body: Stack( clipBehavior: Clip.none, children: [ - Positioned( - left: -20, - top: -20, - right: -20, - bottom: -20, - child: Image.asset( - 'assets/images/bg_welcome.png', - fit: BoxFit.cover, - ), - ), + Positioned(left: -20, top: -20, right: -20, bottom: -20, child: Image.asset('assets/images/bg_welcome.png', fit: BoxFit.cover)), Positioned.fill( child: SafeArea( child: Padding( @@ -48,47 +40,26 @@ class UpdateScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), - const Text( - 'Пора обновиться! ⚡', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.w800, - height: 1, - ), - ), + const Text('Пора обновиться! ⚡', style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800, height: 1)), const SizedBox(height: 20), const Text( 'Мы выпустили важное обновление\nс новыми функциями и исправлениями.\nЧтобы продолжить пользоваться\nприложением без сбоев, обновите\nего прямо сейчас.', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - height: 1.15, - ), + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1.15), ), const SizedBox(height: 32), InkWell( onTap: () async { - final Uri url = Platform.isAndroid - ? Uri.parse('https://www.rustore.ru/catalog/app/com.eatvioapp.app') - : Uri.parse('https://apps.apple.com/app/idYOUR_APP_ID'); + final Uri url = Platform.isAndroid ? Uri.parse(EnvConfig.rustoreUrl) : Uri.parse(EnvConfig.appStoreUrl); await launchUrl(url); }, borderRadius: BorderRadius.circular(8), child: Ink( height: 52, - decoration: BoxDecoration( - color: appTheme.blueA400, - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(8)), child: const Center( child: Text( 'Обновить', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - height: 1, - color: Colors.white, - ), + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, height: 1, color: Colors.white), ), ), ), diff --git a/lib/features/user_details/presentation/cubit/user_details_cubit.dart b/lib/features/user_details/presentation/cubit/user_details_cubit.dart index c6eb36b7..0ce0bd93 100644 --- a/lib/features/user_details/presentation/cubit/user_details_cubit.dart +++ b/lib/features/user_details/presentation/cubit/user_details_cubit.dart @@ -20,19 +20,11 @@ class UserDetailsCubit extends Cubit { } } - void getChartData({ - String period = '1', - required String slug, - }) async { + void getChartData({String period = '1', required String slug}) async { try { - final weightPoints = await profileRepository.getWeight(period: period); - final circumferencePoints = await profileRepository.getCircumferences(period: period); - emit( - state.copyWith( - weightPoints: weightPoints, - circumferencePoints: circumferencePoints, - ), - ); + final weightPoints = await profileRepository.getWeight(period: period, slug: slug); + final circumferencePoints = await profileRepository.getCircumferences(period: period, slug: slug); + emit(state.copyWith(weightPoints: weightPoints, circumferencePoints: circumferencePoints)); } catch (e, stackTrace) { log(e.toString(), stackTrace: stackTrace); emit(state.copyWith(error: e.toString(), isLoading: false)); diff --git a/lib/features/user_details/presentation/user_details_screen.dart b/lib/features/user_details/presentation/user_details_screen.dart index f79b990e..4907e3dd 100644 --- a/lib/features/user_details/presentation/user_details_screen.dart +++ b/lib/features/user_details/presentation/user_details_screen.dart @@ -31,9 +31,7 @@ class UserDetailsScreen extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (context) => sl()..getUserDetails(slug), - child: UserDetailsContent( - slug: slug, - ), + child: UserDetailsContent(slug: slug), ); } } @@ -79,27 +77,15 @@ class _UserDetailsContentState extends State { actions: [ PortalTarget( visible: isMoreOptionsVisible, - portalFollower: MoreOptions( - path: 'profile/${widget.slug}', - complainedContentType: ComplainedContent.profile, - slug: widget.slug, - ), - anchor: const Aligned( - shiftToWithinBound: AxisFlag(x: true, y: true), - follower: Alignment.topRight, - target: Alignment.bottomRight, - ), + portalFollower: MoreOptions(path: 'profile/${widget.slug}', complainedContentType: ComplainedContent.profile, slug: widget.slug), + anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight), child: IconButton( onPressed: () { setState(() { isMoreOptionsVisible = !isMoreOptionsVisible; }); }, - icon: SvgPicture.asset( - 'assets/icons/more_button.svg', - width: 20, - height: 20, - ), + icon: SvgPicture.asset('assets/icons/more_button.svg', width: 20, height: 20), ), ), ], @@ -124,25 +110,16 @@ class _UserDetailsContentState extends State { initiallyExpanded: true, title: s.general_info, children: [ - Text( - s.date_of_birth, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.date_of_birth, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), CustomTextFormField( readOnly: true, controller: TextEditingController(text: DateTime.parse(profileDetails.birthdate).format('dd.MM.yyyy')), ), const SizedBox(height: 16), - Text( - s.sex, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.sex, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.gender.parseGender()), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.gender.parseGender())), const SizedBox(height: 16), ], ), @@ -155,15 +132,9 @@ class _UserDetailsContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - s.height, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.height, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.height.toString()), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.height.toString())), ], ), ), @@ -172,15 +143,9 @@ class _UserDetailsContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - s.weight, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.weight, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.weight.toStringAsFixed(0)), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.weight.toStringAsFixed(0))), ], ), ), @@ -193,15 +158,9 @@ class _UserDetailsContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - s.chest_circumference, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.chest_circumference, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.chestCircumference.toString()), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.chestCircumference.toString())), ], ), ), @@ -210,15 +169,9 @@ class _UserDetailsContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - s.waist_circumference, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.waist_circumference, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.waistCircumference.toString()), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.waistCircumference.toString())), ], ), ), @@ -227,15 +180,9 @@ class _UserDetailsContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - s.hip_circumference, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.hip_circumference, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.hipCircumference.toString()), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hipCircumference.toString())), ], ), ), @@ -253,15 +200,9 @@ class _UserDetailsContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - s.upper, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.upper, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.bloodPressureUp.toString()), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.bloodPressureUp.toString())), ], ), ), @@ -270,10 +211,7 @@ class _UserDetailsContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - s.lower, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.lower, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), CustomTextFormField( readOnly: true, @@ -291,15 +229,9 @@ class _UserDetailsContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - s.has_hypo, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.has_hypo, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.hasHypo.toString()), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHypo.toString())), ], ), ), @@ -308,15 +240,9 @@ class _UserDetailsContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - s.has_hyper, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.has_hyper, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.hasHyper.toString()), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHyper.toString())), ], ), ), @@ -328,60 +254,27 @@ class _UserDetailsContentState extends State { ExpandableBouble( title: s.other_info, children: [ - Text( - s.has_alergy, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.has_alergy, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.aboutAlergy), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.aboutAlergy)), const SizedBox(height: 16), - Text( - s.takes_pills, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.takes_pills, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.aboutTakingPils), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.aboutTakingPils)), const SizedBox(height: 16), - Text( - s.sport_hobbies, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.sport_hobbies, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.aboutSportHobbies), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.aboutSportHobbies)), const SizedBox(height: 16), - Text( - s.liked_groceries, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.liked_groceries, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.favoriteFoods), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.favoriteFoods)), const SizedBox(height: 16), - Text( - s.not_liked_groceries, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.not_liked_groceries, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField( - readOnly: true, - controller: TextEditingController(text: profileDetails.unfavoriteFoods), - ), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.unfavoriteFoods)), const SizedBox(height: 16), - Text( - s.additional_information, - style: TextStyles.textSMedium.copyWith(color: appTheme.black), - ), + Text(s.additional_information, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), SizedBox( height: 112, @@ -413,10 +306,7 @@ class _UserDetailsContentState extends State { ), ); } - return Scaffold( - appBar: AppBar(), - body: const Loader(), - ); + return Scaffold(appBar: AppBar(), body: const Loader()); }, ); } @@ -432,31 +322,17 @@ class _UserDetailsContentState extends State { SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 16), scrollDirection: Axis.horizontal, - child: Row( - children: [ - _choiceChipWidget(0), - const SizedBox(width: 8), - _choiceChipWidget(1), - const SizedBox(width: 8), - _choiceChipWidget(2), - ], - ), + child: Row(children: [_choiceChipWidget(0), const SizedBox(width: 8), _choiceChipWidget(1), const SizedBox(width: 8), _choiceChipWidget(2)]), ), Builder( builder: (context) { switch (_selectedChipIndex) { case 0: - return WeightChartWidget( - weightPoints: weightPoints, - ); + return WeightChartWidget(weightPoints: weightPoints); case 1: - return CircumferenceChartWidget( - circumferencePoints: circumferencePoints, - ); + return CircumferenceChartWidget(circumferencePoints: circumferencePoints); case 2: - return ImtChartWidget( - weightPoints: weightPoints, - ); + return ImtChartWidget(weightPoints: weightPoints); default: return const SizedBox.shrink(); } @@ -489,16 +365,10 @@ class _UserDetailsContentState extends State { pressElevation: 0, showCheckmark: false, side: BorderSide.none, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), label: Text(chipsIntervalLabelList[index]), - labelStyle: TextStyle( - color: appTheme.gray600, - fontWeight: FontWeight.w500, - fontSize: 12, - ), + labelStyle: TextStyle(color: appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12), selected: _selectedIntervalChipIndex == index, onSelected: (bool selected) { context.read().getChartData(period: chipsIntervalList[index], slug: widget.slug); @@ -515,16 +385,10 @@ class _UserDetailsContentState extends State { pressElevation: 0, showCheckmark: false, side: BorderSide.none, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), label: Text(chipsLabelList[index]), - labelStyle: TextStyle( - color: index == _selectedChipIndex ? Colors.white : appTheme.gray600, - fontWeight: FontWeight.w500, - fontSize: 12, - ), + labelStyle: TextStyle(color: index == _selectedChipIndex ? Colors.white : appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12), selected: _selectedChipIndex == index, backgroundColor: appTheme.gray100, onSelected: (bool selected) { diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index dcf61a5d..6450332e 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -1,8 +1,8 @@ // File generated by FlutterFire CLI. // ignore_for_file: type=lint import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; -import 'package:flutter/foundation.dart' - show defaultTargetPlatform, kIsWeb, TargetPlatform; +import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; +import 'package:eatvio_app/core/env_config.dart'; /// Default [FirebaseOptions] for use with your Firebase apps. /// @@ -43,28 +43,26 @@ class DefaultFirebaseOptions { 'you can reconfigure this by running the FlutterFire CLI again.', ); default: - throw UnsupportedError( - 'DefaultFirebaseOptions are not supported for this platform.', - ); + throw UnsupportedError('DefaultFirebaseOptions are not supported for this platform.'); } } - static const FirebaseOptions android = FirebaseOptions( - apiKey: 'AIzaSyBUe1UqAAMfgMcd4hWdRoLF1Os-yn-HE9k', - appId: '1:613980912922:android:4de95e5f2d50ba69ecc0a3', - messagingSenderId: '613980912922', - projectId: 'eatvio-3467b', - databaseURL: 'https://eatvio-3467b-default-rtdb.europe-west1.firebasedatabase.app', - storageBucket: 'eatvio-3467b.firebasestorage.app', + static FirebaseOptions get android => FirebaseOptions( + apiKey: EnvConfig.firebaseAndroidApiKey, + appId: EnvConfig.firebaseAndroidAppId, + messagingSenderId: EnvConfig.firebaseMessagingSenderId, + projectId: EnvConfig.firebaseProjectId, + databaseURL: EnvConfig.firebaseDatabaseUrl, + storageBucket: EnvConfig.firebaseStorageBucket, ); - static const FirebaseOptions ios = FirebaseOptions( - apiKey: 'AIzaSyBnE8tQjL9_1HuXrTnqNy6qbXpoenO36yQ', - appId: '1:613980912922:ios:c557d140a1212e63ecc0a3', - messagingSenderId: '613980912922', - projectId: 'eatvio-3467b', - databaseURL: 'https://eatvio-3467b-default-rtdb.europe-west1.firebasedatabase.app', - storageBucket: 'eatvio-3467b.firebasestorage.app', - iosBundleId: 'com.eatvioapp.app', + static FirebaseOptions get ios => FirebaseOptions( + apiKey: EnvConfig.firebaseIosApiKey, + appId: EnvConfig.firebaseIosAppId, + messagingSenderId: EnvConfig.firebaseMessagingSenderId, + projectId: EnvConfig.firebaseProjectId, + databaseURL: EnvConfig.firebaseDatabaseUrl, + storageBucket: EnvConfig.firebaseStorageBucket, + iosBundleId: EnvConfig.firebaseIosBundleId, ); } diff --git a/lib/main.dart b/lib/main.dart index 3fd39e7f..8147c18a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'dart:io' as io; import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/locator_service.dart' as di; import 'package:eatvio_app/core/locator_service.dart'; @@ -12,6 +13,7 @@ import 'package:eatvio_app/firebase_options.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -19,22 +21,28 @@ import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:keyboard_dismisser/keyboard_dismisser.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:eatvio_app/core/env_config.dart'; Future main() async { WidgetsBinding widgetsBinding = SentryWidgetsFlutterBinding.ensureInitialized(); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + + await dotenv.load(fileName: "env/.env"); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await FirebaseMessaging.instance.requestPermission(); await di.init(); - await SentryFlutter.init( - (options) { - options.dsn = 'http://ba7f2653067776f3034e6e89850e85a1@sentry.md-desk.ru:9000/2'; - options.tracesSampleRate = 1.0; - options.debug = false; - }, - appRunner: () => runApp(const MyApp()), - ); + if (kDebugMode) { + io.HttpClient.enableTimelineLogging = true; + } + + await SentryFlutter.init((options) { + options.dsn = EnvConfig.sentryDsn; + options.tracesSampleRate = 1.0; + options.debug = false; + }, appRunner: () => runApp(const MyApp())); } class MyApp extends StatelessWidget { diff --git a/pubspec.yaml b/pubspec.yaml index 0dd1a2da..0154d606 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,7 @@ dependencies: device_info_plus: ^11.5.0 permission_handler: ^12.0.1 qr_code_scanner_plus: ^2.0.10+1 + flutter_dotenv: ^5.1.0 dev_dependencies: build_runner: ^2.6.0 @@ -88,6 +89,7 @@ dev_dependencies: flutter: uses-material-design: true assets: + - env/.env - assets/ - assets/images/ - assets/audio/