diff --git a/.gitea/workflows/test-flight-cd.yaml b/.gitea/workflows/test-flight-cd.yaml
index 4320ecc5..c1244bd9 100644
--- a/.gitea/workflows/test-flight-cd.yaml
+++ b/.gitea/workflows/test-flight-cd.yaml
@@ -11,6 +11,30 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
+ - name: Checkout env repository
+ uses: actions/checkout@v2
+ with:
+ repository: mso7nueh/eatvio_app_env
+ path: env_repo
+ token: ${{ vars.MSO7NUEH_TOKEN }}
+ ref: main
+
+ - name: Copy .env file
+ run: |
+ mkdir -p env
+ cp env_repo/.env env/.env
+
+ - name: Load environment variables from .env
+ run: |
+ set -a
+ source env/.env
+ set +a
+ # Export all variables to GITHUB_ENV
+ while IFS= read -r line; do
+ if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
+ echo "$line" >> $GITHUB_ENV
+ fi
+ done < env/.env
- name: Unlock login keychain
run: |
@@ -36,4 +60,4 @@ jobs:
MATCH_GIT_URL: http://gitea.office.local:3000/mso7nueh/fastlane
FASTLANE_USER: eatvio2023@icloud.com
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ vars.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
- API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
\ No newline at end of file
+ API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml
index fd291367..c7aefd5c 100644
--- a/.gitea/workflows/test.yml
+++ b/.gitea/workflows/test.yml
@@ -16,6 +16,31 @@ jobs:
steps:
- uses: actions/checkout@v3
+ - name: Checkout env repository
+ uses: actions/checkout@v3
+ with:
+ repository: mso7nueh/eatvio_app_env
+ path: env_repo
+ token: ${{ vars.MSO7NUEH_TOKEN }}
+ ref: main
+
+ - name: Copy .env file
+ run: |
+ mkdir -p env
+ cp env_repo/.env env/.env
+
+ - name: Load environment variables from .env
+ run: |
+ set -a
+ source env/.env
+ set +a
+ # Export all variables to GITHUB_ENV
+ while IFS= read -r line; do
+ if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
+ echo "$line" >> $GITHUB_ENV
+ fi
+ done < env/.env
+
- name: Create key.properties file
run: |
cat > android/key.properties <
diff --git a/env/.env b/env/.env
new file mode 100644
index 00000000..e69de29b
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 9a27847e..6388d648 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -396,7 +396,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 64;
+ CURRENT_PROJECT_VERSION = 65;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -546,7 +546,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 64;
+ CURRENT_PROJECT_VERSION = 65;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -584,7 +584,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 64;
+ CURRENT_PROJECT_VERSION = 65;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 2341e63f..9a16914c 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -13,7 +13,7 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- eatvio_app
+ Итвио
CFBundlePackageType
APPL
CFBundleShortVersionString
@@ -21,7 +21,7 @@
CFBundleSignature
????
CFBundleVersion
- 64
+ 65
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
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 GoRouter.of(context).push(MyPlansScreen.path)),
- _divider(context),
- _tile(
- leading: 'assets/icons/finance.svg',
- title: 'Мои финансы',
- onTap: () {
- if (profileModel.roles.contains('consultant')) {
- GoRouter.of(context).push(ConsultantFinancesScreen.path);
- return;
- }
- GoRouter.of(context).push(UserPurchasesScreen.path);
- },
- ),
+ if (!hidePlans) ...[
+ _divider(context),
+ _tile(
+ leading: 'assets/icons/finance.svg',
+ title: 'Мои финансы',
+ onTap: () {
+ if (profileModel.roles.contains('consultant')) {
+ GoRouter.of(context).push(ConsultantFinancesScreen.path);
+ return;
+ }
+ GoRouter.of(context).push(UserPurchasesScreen.path);
+ },
+ ),
+ ],
_divider(context),
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
_divider(context),
diff --git a/lib/features/notifications/models/notification_model.dart b/lib/features/notifications/models/notification_model.dart
index 827a0904..96626d5d 100644
--- a/lib/features/notifications/models/notification_model.dart
+++ b/lib/features/notifications/models/notification_model.dart
@@ -1,8 +1,7 @@
-import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
-
sealed class BaseNotificationModel {
abstract final NotificationType type;
abstract final bool isRead;
+ abstract final DateTime createdAt;
}
class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationModel {
@@ -13,6 +12,8 @@ class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationMod
final String planName;
final String customerName;
final String chatId;
+ @override
+ final DateTime createdAt;
PlanSubscriptionStartedToAuthorNotification({
required this.type,
@@ -20,15 +21,17 @@ class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationMod
required this.customerName,
required this.chatId,
required this.isRead,
+ required this.createdAt,
});
- factory PlanSubscriptionStartedToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory PlanSubscriptionStartedToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return PlanSubscriptionStartedToAuthorNotification(
type: type,
planName: json['plan_name'],
customerName: json['user_name'],
chatId: json['chat_id'],
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -40,20 +43,24 @@ class PlanSubscriptionCanceledToAuthorNotification implements BaseNotificationMo
final bool isRead;
final String planName;
final String customerName;
+ @override
+ final DateTime createdAt;
PlanSubscriptionCanceledToAuthorNotification({
required this.type,
required this.planName,
required this.customerName,
required this.isRead,
+ required this.createdAt,
});
- factory PlanSubscriptionCanceledToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory PlanSubscriptionCanceledToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return PlanSubscriptionCanceledToAuthorNotification(
type: type,
planName: json['plan_name'],
customerName: json['user_name'],
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -65,20 +72,18 @@ class ChosenPlanStartedToClientNotification implements BaseNotificationModel {
final bool isRead;
final String planName;
final int planId;
+ @override
+ final DateTime createdAt;
- ChosenPlanStartedToClientNotification({
- required this.type,
- required this.planName,
- required this.planId,
- required this.isRead,
- });
+ ChosenPlanStartedToClientNotification({required this.type, required this.planName, required this.planId, required this.isRead, required this.createdAt});
- factory ChosenPlanStartedToClientNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory ChosenPlanStartedToClientNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanStartedToClientNotification(
type: type,
planName: json['plan_name'],
planId: json['plan_id'],
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -89,15 +94,18 @@ class ChosenPlanTomorrowReminderToClientNotification implements BaseNotification
@override
final bool isRead;
final String planName;
+ @override
+ final DateTime createdAt;
- ChosenPlanTomorrowReminderToClientNotification({
- required this.type,
- required this.planName,
- required this.isRead,
- });
+ ChosenPlanTomorrowReminderToClientNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
- factory ChosenPlanTomorrowReminderToClientNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
- return ChosenPlanTomorrowReminderToClientNotification(type: type, planName: json['plan_name'], isRead: isRead ?? false);
+ factory ChosenPlanTomorrowReminderToClientNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return ChosenPlanTomorrowReminderToClientNotification(
+ type: type,
+ planName: json['plan_name'],
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ );
}
}
@@ -106,11 +114,17 @@ class DiaryReminderNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ @override
+ final DateTime createdAt;
- DiaryReminderNotification({required this.type, required this.isRead});
+ DiaryReminderNotification({required this.type, required this.isRead, required this.createdAt});
- factory DiaryReminderNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
- return DiaryReminderNotification(type: type, isRead: isRead ?? false);
+ factory DiaryReminderNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return DiaryReminderNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ );
}
}
@@ -120,18 +134,17 @@ class ConsultantLeftCommentNotification implements BaseNotificationModel {
@override
final bool isRead;
final DateTime date;
+ @override
+ final DateTime createdAt;
- ConsultantLeftCommentNotification({
- required this.type,
- required this.date,
- required this.isRead,
- });
+ ConsultantLeftCommentNotification({required this.type, required this.date, required this.isRead, required this.createdAt});
- factory ConsultantLeftCommentNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory ConsultantLeftCommentNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ConsultantLeftCommentNotification(
type: type,
- date: parseDate(json['diary_date']),
+ date: parseDate(json['diary_date'] ?? DateTime.now().toIso8601String()),
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -141,16 +154,16 @@ class PersonalizedDietIsDefinedNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ @override
+ final DateTime createdAt;
- PersonalizedDietIsDefinedNotification({
- required this.type,
- required this.isRead,
- });
+ PersonalizedDietIsDefinedNotification({required this.type, required this.isRead, required this.createdAt});
- factory PersonalizedDietIsDefinedNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory PersonalizedDietIsDefinedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return PersonalizedDietIsDefinedNotification(
type: type,
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -161,17 +174,16 @@ class ChosenPlanFinishedToClientNotification implements BaseNotificationModel {
@override
final bool isRead;
final String planName;
- ChosenPlanFinishedToClientNotification({
- required this.type,
- required this.planName,
- required this.isRead,
- });
+ @override
+ final DateTime createdAt;
+ ChosenPlanFinishedToClientNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
- factory ChosenPlanFinishedToClientNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory ChosenPlanFinishedToClientNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanFinishedToClientNotification(
type: type,
planName: json['plan_name'],
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -182,17 +194,16 @@ class PlanSubscriptionCanceledToCustomerNotification implements BaseNotification
@override
final bool isRead;
final String planName;
- PlanSubscriptionCanceledToCustomerNotification({
- required this.type,
- required this.planName,
- required this.isRead,
- });
+ @override
+ final DateTime createdAt;
+ PlanSubscriptionCanceledToCustomerNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
- factory PlanSubscriptionCanceledToCustomerNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory PlanSubscriptionCanceledToCustomerNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return PlanSubscriptionCanceledToCustomerNotification(
type: type,
planName: json['plan_name'],
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -204,19 +215,23 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel
final bool isRead;
final String planName;
final String customerName;
+ @override
+ final DateTime createdAt;
ChosenPlanStartedToConsultantNotification({
required this.type,
required this.isRead,
required this.planName,
required this.customerName,
+ required this.createdAt,
});
- factory ChosenPlanStartedToConsultantNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory ChosenPlanStartedToConsultantNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanStartedToConsultantNotification(
type: type,
isRead: isRead ?? false,
planName: json['plan_name'],
customerName: json['client_name'],
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -228,19 +243,23 @@ class ChosenPlanTomorrowReminderToConsultantNotification implements BaseNotifica
final bool isRead;
final String planName;
final String customerName;
+ @override
+ final DateTime createdAt;
ChosenPlanTomorrowReminderToConsultantNotification({
required this.type,
required this.planName,
required this.customerName,
required this.isRead,
+ required this.createdAt,
});
- factory ChosenPlanTomorrowReminderToConsultantNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory ChosenPlanTomorrowReminderToConsultantNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanTomorrowReminderToConsultantNotification(
type: type,
planName: json['plan_name'],
customerName: json['client_name'],
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -252,19 +271,23 @@ class PlanSubscriptionPaidToAuthorNotification implements BaseNotificationModel
final bool isRead;
final String planName;
final String customerName;
+ @override
+ final DateTime createdAt;
PlanSubscriptionPaidToAuthorNotification({
required this.type,
required this.isRead,
required this.planName,
required this.customerName,
+ required this.createdAt,
});
- factory PlanSubscriptionPaidToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory PlanSubscriptionPaidToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return PlanSubscriptionPaidToAuthorNotification(
type: type,
isRead: isRead ?? false,
planName: json['plan_name'],
customerName: json['user_name'],
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -277,21 +300,25 @@ class ChosenPlanFinishedToConsultantNotification implements BaseNotificationMode
final String planName;
final String customerName;
final int planId;
+ @override
+ final DateTime createdAt;
ChosenPlanFinishedToConsultantNotification({
required this.type,
required this.planName,
required this.customerName,
required this.planId,
required this.isRead,
+ required this.createdAt,
});
- factory ChosenPlanFinishedToConsultantNotification.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory ChosenPlanFinishedToConsultantNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanFinishedToConsultantNotification(
type: type,
planName: json['plan_name'],
customerName: json['client_name'],
planId: json['plan_id'],
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -301,16 +328,212 @@ class ChosenPlanLastDayFinishedForUser implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ @override
+ final DateTime createdAt;
- ChosenPlanLastDayFinishedForUser({
- required this.type,
- required this.isRead,
- });
+ ChosenPlanLastDayFinishedForUser({required this.type, required this.isRead, required this.createdAt});
- factory ChosenPlanLastDayFinishedForUser.fromJson(Map json, NotificationType type, {bool? isRead}) {
+ factory ChosenPlanLastDayFinishedForUser.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanLastDayFinishedForUser(
type: type,
isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ );
+ }
+}
+
+class ChosenPlanConsultantLastDayStarted implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ final String planName;
+ final int planId;
+ final String chatId;
+ final String chatUrl;
+ @override
+ final DateTime createdAt;
+
+ ChosenPlanConsultantLastDayStarted({
+ required this.type,
+ required this.planName,
+ required this.planId,
+ required this.chatId,
+ required this.chatUrl,
+ required this.isRead,
+ required this.createdAt,
+ });
+
+ factory ChosenPlanConsultantLastDayStarted.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return ChosenPlanConsultantLastDayStarted(
+ type: type,
+ planName: json['plan_name'],
+ planId: json['plan_id'],
+ chatId: json['chat_id'],
+ chatUrl: json['chat_url'],
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ );
+ }
+}
+
+class ComplaintToModerationForUser implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ final String message;
+ final int entityId;
+ final String entityType;
+ @override
+ final DateTime createdAt;
+
+ ComplaintToModerationForUser({
+ required this.type,
+ required this.message,
+ required this.entityId,
+ required this.entityType,
+ required this.isRead,
+ required this.createdAt,
+ });
+
+ factory ComplaintToModerationForUser.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return ComplaintToModerationForUser(
+ type: type,
+ message: json['message'],
+ entityId: json['entity_id'],
+ entityType: json['entity_type'],
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ );
+ }
+}
+
+class CommentReplied implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ final String entityName;
+ final int commentedEntityId;
+ final String commentedEntityType;
+ final String author;
+ final int authorId;
+ final int parentCommentId;
+ @override
+ final DateTime createdAt;
+
+ CommentReplied({
+ required this.type,
+ required this.entityName,
+ required this.commentedEntityId,
+ required this.commentedEntityType,
+ required this.author,
+ required this.authorId,
+ required this.parentCommentId,
+ required this.isRead,
+ required this.createdAt,
+ });
+
+ factory CommentReplied.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return CommentReplied(
+ type: type,
+ entityName: json['entity_name'],
+ commentedEntityId: json['commented_entity_id'],
+ commentedEntityType: json['commented_entity_type'],
+ author: json['author'],
+ authorId: json['author_id'],
+ parentCommentId: json['parrent_comment_id'],
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ );
+ }
+}
+
+class ScoreAdded implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ final String scoreKey;
+ final int scorableId;
+ final String scorableType;
+ final String scorableName;
+ final String scorableSlug;
+ final int scoreValue;
+ @override
+ final DateTime createdAt;
+
+ ScoreAdded({
+ required this.type,
+ required this.scoreKey,
+ required this.scorableId,
+ required this.scorableType,
+ required this.scorableName,
+ required this.scorableSlug,
+ required this.scoreValue,
+ required this.isRead,
+ required this.createdAt,
+ });
+
+ factory ScoreAdded.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return ScoreAdded(
+ type: type,
+ scoreKey: json['score_key'],
+ scorableId: json['scorable_id'],
+ scorableType: json['scorable_type'],
+ scorableName: json['scorable_name'] ?? '',
+ scorableSlug: json['scorable_slug'] ?? '',
+ scoreValue: json['score_value'],
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ );
+ }
+}
+
+class EntityCommented implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ final String entityName;
+ final int entityId;
+ final String entityType;
+ final String entityTranslatedType;
+ final String entityUrl;
+ final String authorName;
+ final int authorId;
+ final String authorSlug;
+ @override
+ final DateTime createdAt;
+
+ EntityCommented({
+ required this.type,
+ required this.entityName,
+ required this.entityId,
+ required this.entityType,
+ required this.entityTranslatedType,
+ required this.entityUrl,
+ required this.authorName,
+ required this.authorId,
+ required this.authorSlug,
+ required this.isRead,
+ required this.createdAt,
+ });
+
+ factory EntityCommented.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return EntityCommented(
+ type: type,
+ entityName: json['entity_name'],
+ entityId: json['entity_id'],
+ entityType: json['entity_type'],
+ entityTranslatedType: json['entity_traslated_type'],
+ entityUrl: json['entity_url'],
+ authorName: json['author_name'],
+ authorId: json['author_id'],
+ authorSlug: json['author_slug'],
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
@@ -320,11 +543,10 @@ class UnknownNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ @override
+ final DateTime createdAt;
- UnknownNotification({
- required this.type,
- this.isRead = true,
- });
+ UnknownNotification({required this.type, this.isRead = true, DateTime? createdAt}) : createdAt = createdAt ?? DateTime.fromMillisecondsSinceEpoch(0);
}
enum NotificationType {
@@ -342,23 +564,37 @@ enum NotificationType {
planSubscriptionPaidToAuthor,
chosenPlanFinishedToConsultant,
chosenPlanLastDayFinishedForUser,
+ // Новые типы
+ chosenPlanConsultantLastDayStarted,
+ complaintToModerationForUser,
+ commentReplied,
+ scoreAdded,
+ entityCommented,
unknown;
static NotificationType getTypeFromString(String type) => switch (type) {
- 'App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor' => NotificationType.planSubscriptionStartedToAuthor,
- 'App\\Notifications\\Plan\\PlanSubscriptionCanceledToAuthor' => NotificationType.planSubscriptionCanceledToAuthor,
- 'App\\Notifications\\Plan\\ChosenPlanStartedToClientNotification' => NotificationType.chosenPlanStartedToClientNotification,
- 'App\\Notifications\\Plan\\ChosenPlanTomorrowReminderToClientNotification' => NotificationType.chosenPlanStartedToClientNotification,
- 'App\\Notifications\\DiaryReminderNotification' => NotificationType.diaryReminderNotification,
- 'App\\Notifications\\Plan\\ConsultantLeftComment' => NotificationType.consultantLeftComment,
- 'App\\Notifications\\Plan\\PersonalizedDietIsDefined' => NotificationType.personalizedDietIsDefined,
- 'App\\Notifications\\Plan\\ChosenPlanFinishedToClient' => NotificationType.chosenPlanFinishedToClient,
- 'App\\Notifications\\Plan\\PlanSubscriptionCanceledToCustomer' => NotificationType.planSubscriptionCanceledToCustomer,
- 'App\\Notifications\\Plan\\ChosenPlanStartedToConsultantNotification' => NotificationType.chosenPlanStartedToConsultantNotification,
- 'App\\Notifications\\Plan\\ChosenPlanTomorrowReminderToConsultantNotification' => NotificationType.chosenPlanTomorrowReminderToConsultantNotification,
- 'App\\Notifications\\Plan\\PlanSubscriptionPaidToAuthor' => NotificationType.planSubscriptionPaidToAuthor,
- 'App\\Notifications\\Plan\\ChosenPlanFinishedToConsultant' => NotificationType.chosenPlanFinishedToConsultant,
- 'App\\Notifications\\ChosenPlan\\LastDayFinishedForUser' => NotificationType.chosenPlanLastDayFinishedForUser,
- _ => NotificationType.unknown,
- };
+ 'App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor' => NotificationType.planSubscriptionStartedToAuthor,
+ 'App\\Notifications\\Plan\\PlanSubscriptionCanceledToAuthor' => NotificationType.planSubscriptionCanceledToAuthor,
+ 'App\\Notifications\\Plan\\ChosenPlanStartedToClientNotification' => NotificationType.chosenPlanStartedToClientNotification,
+ 'App\\Notifications\\Plan\\ChosenPlanTomorrowReminderToClientNotification' => NotificationType.chosenPlanStartedToClientNotification,
+ 'App\\Notifications\\DiaryReminderNotification' => NotificationType.diaryReminderNotification,
+ 'App\\Notifications\\Plan\\ConsultantLeftComment' => NotificationType.consultantLeftComment,
+ 'App\\Notifications\\Plan\\PersonalizedDietIsDefined' => NotificationType.personalizedDietIsDefined,
+ 'App\\Notifications\\Plan\\ChosenPlanFinishedToClient' => NotificationType.chosenPlanFinishedToClient,
+ 'App\\Notifications\\Plan\\PlanSubscriptionCanceledToCustomer' => NotificationType.planSubscriptionCanceledToCustomer,
+ 'App\\Notifications\\Plan\\ChosenPlanStartedToConsultantNotification' => NotificationType.chosenPlanStartedToConsultantNotification,
+ 'App\\Notifications\\Plan\\ChosenPlanTomorrowReminderToConsultantNotification' => NotificationType.chosenPlanTomorrowReminderToConsultantNotification,
+ 'App\\Notifications\\Plan\\PlanSubscriptionPaidToAuthor' => NotificationType.planSubscriptionPaidToAuthor,
+ 'App\\Notifications\\Plan\\ChosenPlanFinishedToConsultant' => NotificationType.chosenPlanFinishedToConsultant,
+ 'App\\Notifications\\ChosenPlan\\LastDayFinishedForUser' => NotificationType.chosenPlanLastDayFinishedForUser,
+ // Новые маппинги
+ 'App\\Notifications\\ChosenPlan\\LastDayStartedForConsultant' => NotificationType.chosenPlanConsultantLastDayStarted,
+ 'App\\Notifications\\ComplaintToModerationNotify' => NotificationType.complaintToModerationForUser,
+ 'App\\Notifications\\Comment\\Replied' => NotificationType.commentReplied,
+ 'App\\Notifications\\Score\\ScoreAdded' => NotificationType.scoreAdded,
+ 'App\\Notifications\\Comment\\EntityCommented' => NotificationType.entityCommented,
+ _ => NotificationType.unknown,
+ };
}
+
+DateTime parseDate(String date) => DateTime.parse(date).toLocal();
diff --git a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart
index 61eba9a7..d4e6e17d 100644
--- a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart
+++ b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart
@@ -13,10 +13,7 @@ class NotificationsCubit extends Cubit {
final WsManager wsManager;
final NotificationsRepository notificationsRepository;
StreamSubscription? _newNotificationsSubscription;
- NotificationsCubit({
- required this.wsManager,
- required this.notificationsRepository,
- }) : super(const NotificationsState.empty());
+ NotificationsCubit({required this.wsManager, required this.notificationsRepository}) : super(const NotificationsState.empty());
Future connectToWebSocket() async {
await _newNotificationsSubscription?.cancel();
@@ -27,22 +24,95 @@ class NotificationsCubit extends Cubit {
BaseNotificationModel _parseNotification(Map notificationJson, NotificationType type) {
try {
+ final createdAt = notificationJson['created_at'];
+ final isRead = notificationJson['read_at'] != null;
+
return switch (type) {
- NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(notificationJson, type),
- NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson, type),
- NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(notificationJson, type),
- NotificationType.chosenPlanTomorrowReminderToClientNotification => ChosenPlanTomorrowReminderToClientNotification.fromJson(notificationJson, type),
- NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson, type),
- NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson, type),
- NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(notificationJson, type),
- NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(notificationJson, type),
- NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson, type),
- NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromJson(notificationJson, type),
- NotificationType.chosenPlanTomorrowReminderToConsultantNotification =>
- ChosenPlanTomorrowReminderToConsultantNotification.fromJson(notificationJson, type),
- NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromJson(notificationJson, type),
- NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(notificationJson, type),
- NotificationType.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.fromJson(notificationJson, type),
+ NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenPlanTomorrowReminderToClientNotification => ChosenPlanTomorrowReminderToClientNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenPlanConsultantLastDayStarted => ChosenPlanConsultantLastDayStarted.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
+ NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
+ NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenPlanTomorrowReminderToConsultantNotification => ChosenPlanTomorrowReminderToConsultantNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ // New notification types
+ NotificationType.complaintToModerationForUser => ComplaintToModerationForUser.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
+ NotificationType.commentReplied => CommentReplied.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
+ NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
+ NotificationType.entityCommented => EntityCommented.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.unknown => UnknownNotification(type: type),
};
} catch (e) {
@@ -56,32 +126,80 @@ class NotificationsCubit extends Cubit {
final type = NotificationType.getTypeFromString(stringifiedType);
final notification = _parseNotification(json, type);
_notifications.insert(0, notification);
- emit(
- NotificationsState.loaded(
- notifications: _notifications,
- notificationsCount: _notifications.length,
- hasUnreadNotifications: true,
- ),
- );
+
+ final currentState = state;
+ if (currentState is NotificationsStateLoaded) {
+ emit(currentState.copyWith(notifications: _notifications, notificationsCount: _notifications.length, hasUnreadNotifications: true));
+ }
}
Future getNotifications() async {
try {
- final notifications = await notificationsRepository.getNotifications(page: 1);
- _notifications.clear();
- _notifications.addAll(notifications ?? []);
- emit(
- NotificationsState.loaded(
- notifications: _notifications,
- notificationsCount: _notifications.length,
- hasUnreadNotifications: (notifications ?? []).where((elem) => elem.isRead == false).isNotEmpty,
- ),
- );
+ final result = await notificationsRepository.getNotifications(page: 1);
+ if (result != null) {
+ final notifications = result['notifications'] as List;
+ final pagination = result['pagination'] as Map;
+
+ _notifications.clear();
+ _notifications.addAll(notifications);
+
+ final currentPage = pagination['current_page'] as int;
+ final lastPage = pagination['last_page'] as int;
+ final hasNextPage = currentPage < lastPage;
+
+ emit(
+ NotificationsState.loaded(
+ notifications: _notifications,
+ notificationsCount: _notifications.length,
+ hasUnreadNotifications: notifications.where((elem) => elem.isRead == false).isNotEmpty,
+ currentPage: currentPage,
+ hasNextPage: hasNextPage,
+ ),
+ );
+ }
} catch (e, st) {
log(e.toString(), stackTrace: st);
}
}
+ Future loadMoreNotifications() async {
+ final currentState = state;
+ if (currentState is NotificationsStateLoaded && currentState.hasNextPage && !currentState.isLoadingMore) {
+ try {
+ emit(currentState.copyWith(isLoadingMore: true));
+
+ final nextPage = currentState.currentPage + 1;
+ final result = await notificationsRepository.getNotifications(page: nextPage);
+
+ if (result != null) {
+ final newNotifications = result['notifications'] as List;
+ final pagination = result['pagination'] as Map;
+
+ _notifications.addAll(newNotifications);
+
+ final currentPage = pagination['current_page'] as int;
+ final lastPage = pagination['last_page'] as int;
+ final hasNextPage = currentPage < lastPage;
+
+ emit(
+ currentState.copyWith(
+ notifications: _notifications,
+ notificationsCount: _notifications.length,
+ currentPage: currentPage,
+ hasNextPage: hasNextPage,
+ isLoadingMore: false,
+ ),
+ );
+ } else {
+ emit(currentState.copyWith(isLoadingMore: false));
+ }
+ } catch (e, st) {
+ log(e.toString(), stackTrace: st);
+ emit(currentState.copyWith(isLoadingMore: false));
+ }
+ }
+ }
+
Future markNotificationsAsRead() async {
final currentState = state;
if (currentState is NotificationsStateLoaded) {
diff --git a/lib/features/notifications/presentation/notifications_cubit/notifications_state.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_state.dart
index 9c221b31..391e199e 100644
--- a/lib/features/notifications/presentation/notifications_cubit/notifications_state.dart
+++ b/lib/features/notifications/presentation/notifications_cubit/notifications_state.dart
@@ -14,6 +14,9 @@ abstract class NotificationsState with _$NotificationsState {
required int notificationsCount,
@Default(false) bool isLoading,
@Default(false) bool hasUnreadNotifications,
+ @Default(1) int currentPage,
+ @Default(true) bool hasNextPage,
+ @Default(false) bool isLoadingMore,
}) = NotificationsStateLoaded;
const factory NotificationsState.error({
diff --git a/lib/features/notifications/presentation/notifications_screen.dart b/lib/features/notifications/presentation/notifications_screen.dart
index ff48617f..58e3c65f 100644
--- a/lib/features/notifications/presentation/notifications_screen.dart
+++ b/lib/features/notifications/presentation/notifications_screen.dart
@@ -10,9 +10,38 @@ import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-class NotificationsScreen extends StatelessWidget {
+class NotificationsScreen extends StatefulWidget {
const NotificationsScreen({super.key});
+ @override
+ State createState() => _NotificationsScreenState();
+}
+
+class _NotificationsScreenState extends State {
+ final ScrollController _scrollController = ScrollController();
+
+ @override
+ void initState() {
+ super.initState();
+ _scrollController.addListener(_onScroll);
+ }
+
+ @override
+ void dispose() {
+ _scrollController.dispose();
+ super.dispose();
+ }
+
+ void _onScroll() {
+ if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) {
+ final cubit = context.read();
+ final state = cubit.state;
+ if (state is NotificationsStateLoaded && state.hasNextPage && !state.isLoadingMore) {
+ cubit.loadMoreNotifications();
+ }
+ }
+ }
+
@override
Widget build(BuildContext context) {
final s = S.of(context);
@@ -20,62 +49,65 @@ class NotificationsScreen extends StatelessWidget {
builder: (BuildContext context, NotificationsState state) {
return Scaffold(
backgroundColor: appTheme.gray100,
- appBar: AppBar(
- title: const Text('Уведомления'),
- ),
+ appBar: AppBar(title: const Text('Уведомления')),
body: state is NotificationsStateLoaded
- ? Stack(children: [
- Padding(
- padding: const EdgeInsets.only(top: 8.0),
- child: Container(
- padding: const EdgeInsets.only(top: 16),
- decoration: BoxDecoration(
- color: appTheme.whiteA700,
- borderRadius: const BorderRadius.only(
- topLeft: Radius.circular(12),
- topRight: Radius.circular(12),
+ ? Stack(
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(top: 8.0),
+ child: Container(
+ padding: const EdgeInsets.only(top: 16),
+ decoration: BoxDecoration(
+ color: appTheme.whiteA700,
+ borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
+ ),
+ child: state.isLoading
+ ? const Center(child: Loader())
+ : CustomScrollView(
+ controller: _scrollController,
+ slivers: [
+ SliverList.separated(
+ separatorBuilder: (context, index) =>
+ const Padding(padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), child: CustomDivider()),
+ itemBuilder: (context, index) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: NotificationWidget(notification: state.notifications[index]),
+ );
+ },
+ itemCount: state.notifications.length,
+ ),
+ if (state.isLoadingMore)
+ const SliverToBoxAdapter(
+ child: Padding(
+ padding: EdgeInsets.all(16.0),
+ child: Center(child: Loader()),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: Padding(
+ padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
+ child: CustomOutlinedButton(
+ text: s.markAsRead,
+ height: 52,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(
+ side: const WidgetStatePropertyAll(BorderSide.none),
+ backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
+ ),
+ onPressed: () async {
+ context.read().markNotificationsAsRead();
+ },
),
),
- child: state.isLoading
- ? const Center(child: Loader())
- : CustomScrollView(
- slivers: [
- SliverList.separated(
- separatorBuilder: (context, index) => const Padding(
- padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
- child: CustomDivider(),
- ),
- itemBuilder: (context, index) {
- return Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: NotificationWidget(
- notification: state.notifications[index],
- ),
- );
- },
- itemCount: state.notifications.length,
- )
- ],
- ),
),
- ),
- Align(
- alignment: Alignment.bottomCenter,
- child: Padding(
- padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
- child: CustomOutlinedButton(
- text: s.markAsRead,
- height: 52,
- buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
- buttonStyle:
- ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
- onPressed: () async {
- context.read().markNotificationsAsRead();
- },
- ),
- ),
- )
- ])
+ ],
+ )
: const Placeholder(),
);
},
diff --git a/lib/features/notifications/presentation/widgets/notification_widget.dart b/lib/features/notifications/presentation/widgets/notification_widget.dart
index 66b6cf18..18c02a13 100644
--- a/lib/features/notifications/presentation/widgets/notification_widget.dart
+++ b/lib/features/notifications/presentation/widgets/notification_widget.dart
@@ -15,46 +15,39 @@ class NotificationWidget extends StatelessWidget {
Widget build(BuildContext context) {
return switch (notification) {
PlanSubscriptionStartedToAuthorNotification() => _PlanSubscriptionStartedToAuthorWidget(
- notification: notification as PlanSubscriptionStartedToAuthorNotification,
- ),
+ notification: notification as PlanSubscriptionStartedToAuthorNotification,
+ ),
PlanSubscriptionCanceledToAuthorNotification() => _PlanSubscriptionCanceledToAuthorWidget(
- notification: notification as PlanSubscriptionCanceledToAuthorNotification,
- ),
- ChosenPlanStartedToClientNotification() => _ChosenPlanStartedToClientWidget(
- notification: notification as ChosenPlanStartedToClientNotification,
- ),
+ notification: notification as PlanSubscriptionCanceledToAuthorNotification,
+ ),
+ ChosenPlanStartedToClientNotification() => _ChosenPlanStartedToClientWidget(notification: notification as ChosenPlanStartedToClientNotification),
ChosenPlanTomorrowReminderToClientNotification() => _ChosenPlanTomorrowReminderToClientWidget(
- notification: notification as ChosenPlanTomorrowReminderToClientNotification,
- ),
- DiaryReminderNotification() => _DiaryReminderWidget(
- notification: notification as DiaryReminderNotification,
- ),
- ConsultantLeftCommentNotification() => _ConsultantLeftCommentWidget(
- notification: notification as ConsultantLeftCommentNotification,
- ),
- PersonalizedDietIsDefinedNotification() => _PersonalizedDietIsDefinedWidget(
- notification: notification as PersonalizedDietIsDefinedNotification,
- ),
- ChosenPlanFinishedToClientNotification() => _ChosenPlanFinishedToClientWidget(
- notification: notification as ChosenPlanFinishedToClientNotification,
- ),
+ notification: notification as ChosenPlanTomorrowReminderToClientNotification,
+ ),
+ DiaryReminderNotification() => _DiaryReminderWidget(notification: notification as DiaryReminderNotification),
+ ConsultantLeftCommentNotification() => _ConsultantLeftCommentWidget(notification: notification as ConsultantLeftCommentNotification),
+ PersonalizedDietIsDefinedNotification() => _PersonalizedDietIsDefinedWidget(notification: notification as PersonalizedDietIsDefinedNotification),
+ ChosenPlanFinishedToClientNotification() => _ChosenPlanFinishedToClientWidget(notification: notification as ChosenPlanFinishedToClientNotification),
PlanSubscriptionCanceledToCustomerNotification() => _PlanSubscriptionCanceledToCustomerWidget(
- notification: notification as PlanSubscriptionCanceledToCustomerNotification,
- ),
+ notification: notification as PlanSubscriptionCanceledToCustomerNotification,
+ ),
ChosenPlanStartedToConsultantNotification() => _ChosenPlanStartedToConsultantWidget(
- notification: notification as ChosenPlanStartedToConsultantNotification,
- ),
+ notification: notification as ChosenPlanStartedToConsultantNotification,
+ ),
ChosenPlanTomorrowReminderToConsultantNotification() => _ChosenPlanTomorrowReminderToConsultantWidget(
- notification: notification as ChosenPlanTomorrowReminderToConsultantNotification,
- ),
- PlanSubscriptionPaidToAuthorNotification() => _PlanSubscriptionPaidToAuthorWidget(
- notification: notification as PlanSubscriptionPaidToAuthorNotification,
- ),
+ notification: notification as ChosenPlanTomorrowReminderToConsultantNotification,
+ ),
+ PlanSubscriptionPaidToAuthorNotification() => _PlanSubscriptionPaidToAuthorWidget(notification: notification as PlanSubscriptionPaidToAuthorNotification),
ChosenPlanFinishedToConsultantNotification() => _ChosenPlanFinishedToConsultantWidget(
- notification: notification as ChosenPlanFinishedToConsultantNotification,
- ),
- ChosenPlanLastDayFinishedForUser() => Text((notification as ChosenPlanLastDayFinishedForUser).type.toString()),
- UnknownNotification() => const SizedBox.shrink(),
+ notification: notification as ChosenPlanFinishedToConsultantNotification,
+ ),
+ ChosenPlanLastDayFinishedForUser() => _ChosenPlanLastDayFinishedForUserWidget(notification: notification as ChosenPlanLastDayFinishedForUser),
+ ChosenPlanConsultantLastDayStarted() => _ChosenPlanConsultantLastDayStartedWidget(notification: notification as ChosenPlanConsultantLastDayStarted),
+ ComplaintToModerationForUser() => _ComplaintToModerationForUserWidget(notification: notification as ComplaintToModerationForUser),
+ CommentReplied() => _CommentRepliedWidget(notification: notification as CommentReplied),
+ ScoreAdded() => _ScoreAddedWidget(notification: notification as ScoreAdded),
+ EntityCommented() => _EntityCommentedWidget(notification: notification as EntityCommented),
+ UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification),
};
}
}
@@ -69,6 +62,7 @@ class _PlanSubscriptionStartedToAuthorWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.userSubscribedToPlan(notification.customerName, notification.planName),
+ createdAt: notification.createdAt,
);
}
}
@@ -83,6 +77,7 @@ class _PlanSubscriptionCanceledToAuthorWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.userCanceldThePlanSubscription(notification.customerName, notification.planName),
+ createdAt: notification.createdAt,
);
}
}
@@ -94,10 +89,7 @@ class _ChosenPlanStartedToClientWidget extends StatelessWidget {
Widget build(BuildContext context) {
final s = S.of(context);
- return _BaseNotificationWidget(
- isRead: notification.isRead,
- notificationsText: s.planStarted(notification.planName),
- );
+ return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.planStarted(notification.planName), createdAt: notification.createdAt);
}
}
@@ -111,6 +103,7 @@ class _ChosenPlanTomorrowReminderToClientWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.tomorrowYourPlanWillStart(notification.planName),
+ createdAt: notification.createdAt,
);
}
}
@@ -121,10 +114,7 @@ class _DiaryReminderWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final s = S.of(context);
- return _BaseNotificationWidget(
- isRead: notification.isRead,
- notificationsText: s.fillYasterday,
- );
+ return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.fillYasterday, createdAt: notification.createdAt);
}
}
@@ -138,6 +128,7 @@ class _ConsultantLeftCommentWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.consultantLeftComment(notification.date.format('dd.MM.yyyy')),
+ createdAt: notification.createdAt,
);
}
}
@@ -147,7 +138,7 @@ class _PersonalizedDietIsDefinedWidget extends StatelessWidget {
final PersonalizedDietIsDefinedNotification notification;
@override
Widget build(BuildContext context) {
- return Container();
+ return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Персональная диета определена', createdAt: notification.createdAt);
}
}
@@ -161,6 +152,7 @@ class _ChosenPlanFinishedToClientWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: '${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
+ createdAt: notification.createdAt,
);
}
}
@@ -175,6 +167,7 @@ class _PlanSubscriptionCanceledToCustomerWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.youCanceledSubscriptionToPlan(notification.planName),
+ createdAt: notification.createdAt,
);
}
}
@@ -189,6 +182,7 @@ class _ChosenPlanStartedToConsultantWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.planWithUserStarted(notification.planName, notification.customerName),
+ createdAt: notification.createdAt,
);
}
}
@@ -203,6 +197,7 @@ class _ChosenPlanTomorrowReminderToConsultantWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.tommorowPlanWithUserWillStart(notification.planName, notification.customerName),
+ createdAt: notification.createdAt,
);
}
}
@@ -217,6 +212,7 @@ class _PlanSubscriptionPaidToAuthorWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.userPaidThePlan(notification.customerName, notification.planName),
+ createdAt: notification.createdAt,
);
}
}
@@ -227,58 +223,223 @@ class _ChosenPlanFinishedToConsultantWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final s = S.of(context);
- return Column(children: [
- Text(
- '${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
- style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
- ),
- const SizedBox(height: 12),
- Text(
- s.userConfirmedPlanCompletionWithoutSum,
- style: TextStyles.textSMedium.copyWith(color: appTheme.black),
- ),
- const SizedBox(height: 12),
- CustomOutlinedButton(
- text: s.collectCharge,
- buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
- buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
- onPressed: () {
- GoRouter.of(context).pushNamed(CardSettingsScreen.name, extra: notification.planId);
- },
- ),
- const SizedBox(height: 12),
- ]);
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('${s.congratsYouHaveFinishedPlan} «${notification.planName}»', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)),
+ const SizedBox(height: 12),
+ Text(s.userConfirmedPlanCompletionWithoutSum, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
+ const SizedBox(height: 12),
+ CustomOutlinedButton(
+ text: s.collectCharge,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
+ onPressed: () {
+ GoRouter.of(context).pushNamed(CardSettingsScreen.name, extra: notification.planId);
+ },
+ ),
+ ],
+ ),
+ ),
+ if (!notification.isRead)
+ Container(
+ decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
+ width: 8,
+ height: 8,
+ ),
+ ],
+ ),
+ const SizedBox(height: 4),
+ Text(
+ '${notification.createdAt.day} ${_getMonthName(notification.createdAt.month)} ${notification.createdAt.hour.toString().padLeft(2, '0')}:${notification.createdAt.minute.toString().padLeft(2, '0')}',
+ style: TextStyles.textSMedium.copyWith(color: appTheme.gray600),
+ ),
+ ],
+ );
+ }
+
+ String _getMonthName(int month) {
+ switch (month) {
+ case 1:
+ return 'янв';
+ case 2:
+ return 'фев';
+ case 3:
+ return 'мар';
+ case 4:
+ return 'апр';
+ case 5:
+ return 'май';
+ case 6:
+ return 'июн';
+ case 7:
+ return 'июл';
+ case 8:
+ return 'авг';
+ case 9:
+ return 'сен';
+ case 10:
+ return 'окт';
+ case 11:
+ return 'ноя';
+ case 12:
+ return 'дек';
+ default:
+ return '';
+ }
}
}
// CardSettingsScreen
class _BaseNotificationWidget extends StatelessWidget {
- const _BaseNotificationWidget({required this.notificationsText, required this.isRead});
+ const _BaseNotificationWidget({required this.notificationsText, required this.isRead, required this.createdAt});
final String notificationsText;
final bool isRead;
+ final DateTime createdAt;
@override
Widget build(BuildContext context) {
- return Row(
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
- SizedBox(
- width: MediaQuery.sizeOf(context).width * 0.6,
- child: Text(
- notificationsText,
- style: TextStyles.textMMedium.copyWith(color: appTheme.black),
- ),
- ),
- if (!isRead) ...[
- const Spacer(),
- Container(
- decoration: BoxDecoration(
- color: appTheme.blueA400,
- shape: BoxShape.circle,
+ Row(
+ children: [
+ Expanded(
+ child: Text(notificationsText, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
),
- width: 8,
- height: 8,
- )
- ]
+ if (!isRead)
+ Container(
+ decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
+ width: 8,
+ height: 8,
+ ),
+ ],
+ ),
+ const SizedBox(height: 4),
+ Text(
+ '${createdAt.day} ${_getMonthName(createdAt.month)} ${createdAt.hour.toString().padLeft(2, '0')}:${createdAt.minute.toString().padLeft(2, '0')}',
+ style: TextStyles.textSMedium.copyWith(color: appTheme.gray600),
+ ),
],
);
}
+
+ String _getMonthName(int month) {
+ switch (month) {
+ case 1:
+ return 'янв';
+ case 2:
+ return 'фев';
+ case 3:
+ return 'мар';
+ case 4:
+ return 'апр';
+ case 5:
+ return 'май';
+ case 6:
+ return 'июн';
+ case 7:
+ return 'июл';
+ case 8:
+ return 'авг';
+ case 9:
+ return 'сен';
+ case 10:
+ return 'окт';
+ case 11:
+ return 'ноя';
+ case 12:
+ return 'дек';
+ default:
+ return '';
+ }
+ }
+}
+
+class _ChosenPlanLastDayFinishedForUserWidget extends StatelessWidget {
+ const _ChosenPlanLastDayFinishedForUserWidget({required this.notification});
+ final ChosenPlanLastDayFinishedForUser notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Последний день плана завершен', createdAt: notification.createdAt);
+ }
+}
+
+class _ChosenPlanConsultantLastDayStartedWidget extends StatelessWidget {
+ const _ChosenPlanConsultantLastDayStartedWidget({required this.notification});
+ final ChosenPlanConsultantLastDayStarted notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Последний день плана начался: ${notification.planName}',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _ComplaintToModerationForUserWidget extends StatelessWidget {
+ const _ComplaintToModerationForUserWidget({required this.notification});
+ final ComplaintToModerationForUser notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Жалоба отправлена на модерацию', createdAt: notification.createdAt);
+ }
+}
+
+class _CommentRepliedWidget extends StatelessWidget {
+ const _CommentRepliedWidget({required this.notification});
+ final CommentReplied notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Ответ на комментарий: ${notification.entityName}',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _ScoreAddedWidget extends StatelessWidget {
+ const _ScoreAddedWidget({required this.notification});
+ final ScoreAdded notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Добавлены баллы: ${notification.scoreValue}',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _EntityCommentedWidget extends StatelessWidget {
+ const _EntityCommentedWidget({required this.notification});
+ final EntityCommented notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Комментарий к ${notification.entityTranslatedType}: ${notification.entityName}',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _UnknownNotificationWidget extends StatelessWidget {
+ const _UnknownNotificationWidget({required this.notification});
+ final UnknownNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Неизвестное уведомление: ${notification.type.toString()}',
+ createdAt: notification.createdAt,
+ );
+ }
}
diff --git a/lib/features/notifications/repository/notifications_repository.dart b/lib/features/notifications/repository/notifications_repository.dart
index beef645f..267faa89 100644
--- a/lib/features/notifications/repository/notifications_repository.dart
+++ b/lib/features/notifications/repository/notifications_repository.dart
@@ -1,14 +1,15 @@
-import 'package:eatvio_app/features/notifications/models/notification_model.dart';
+import 'dart:developer';
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
class NotificationsRepository {
final NotificationsService notificationsService;
NotificationsRepository({required this.notificationsService});
- Future?> getNotifications({required int page}) {
+ Future