diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index e6a74c64..d209778b 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -3,7 +3,7 @@ name: Eatvio App Android Test on: [ pull_request ] env: - flutter_version: "3.32.8" + flutter_version: "3.35.1" java_version: "21.x" flutter_channel: "stable" my_local_cache_dir: "/opt/hostedtoolcache/" diff --git a/.gitignore b/.gitignore index 6960fad1..4f05e3a1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,4 @@ *.mocks.dart android/build/ ios/XCBuildData/ -.env -env.example -env/.env +env/.env \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index c9fe6d27..9ffa87c4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -29,7 +29,7 @@ android { defaultConfig { applicationId = "com.eatvioapp.app" - minSdk = 23 + minSdkVersion flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index ac3b4792..e603bd33 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 29eab0d3..e173b2c9 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,11 +18,11 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.7.3" apply false + id "com.android.application" version "8.12.0" apply false // START: FlutterFire Configuration - id "com.google.gms.google-services" version "4.4.2" apply false + id "com.google.gms.google-services" version "4.4.3" apply false // END: FlutterFire Configuration - id "org.jetbrains.kotlin.android" version "2.1.0" apply false + id "org.jetbrains.kotlin.android" version "2.2.10" apply false } include ":app" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index bbf607e7..e484a472 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 = 68; + CURRENT_PROJECT_VERSION = 69; 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 = 68; + CURRENT_PROJECT_VERSION = 69; 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 = 68; + CURRENT_PROJECT_VERSION = 69; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index d9e83314..7e814e8a 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -21,7 +21,7 @@ CFBundleSignature ???? CFBundleVersion - 68 + 69 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/lib/core/credentials_storage.dart b/lib/core/credentials_storage.dart deleted file mode 100644 index 6e9870e5..00000000 --- a/lib/core/credentials_storage.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; - -abstract class CredentialsStorage { - static Future haveCredentialsInStorage() async { - try { - if (await const FlutterSecureStorage().read(key: 'email') == null || await const FlutterSecureStorage().read(key: 'password') == null) { - return false; - } else { - return true; - } - } catch (e) { - return false; - } - } - - static Future> getCredentialsFromStorage() async { - return { - 'email': (await const FlutterSecureStorage().read(key: 'email'))!, - 'password': (await const FlutterSecureStorage().read(key: 'password'))!, - }; - } - - static Future saveCredentials(String email, String password) async { - await const FlutterSecureStorage().write(key: 'email', value: email); - await const FlutterSecureStorage().write(key: 'password', value: password); - } - - static Future removeCredentials() async { - await const FlutterSecureStorage().delete(key: 'email'); - await const FlutterSecureStorage().delete(key: 'password'); - } -} diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 68208bc5..724d8b59 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -107,7 +107,6 @@ import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/se import 'package:eatvio_app/features/settings/repositories/service_repository.dart'; import 'package:eatvio_app/features/settings/services/settings_service.dart'; import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans; @@ -252,5 +251,4 @@ Future init() async { ], ), ); - sl.registerLazySingleton(() async => const FlutterSecureStorage()); } diff --git a/lib/core/token_storage.dart b/lib/core/token_storage.dart index 30906264..f67e919c 100644 --- a/lib/core/token_storage.dart +++ b/lib/core/token_storage.dart @@ -1,9 +1,10 @@ -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; abstract class TokenStorage { static Future haveTokenInStorage() async { + final sharedPreferences = await SharedPreferences.getInstance(); try { - if (await const FlutterSecureStorage().read(key: 'token') == null) { + if (sharedPreferences.getString('token') == null) { return false; } else { return true; @@ -13,19 +14,27 @@ abstract class TokenStorage { } } - static Future getTokenFromStorage() async => await const FlutterSecureStorage().read(key: 'token'); - - static Future saveTokenInStorage(String token) async { - await const FlutterSecureStorage().write(key: 'token', value: token); + static Future getTokenFromStorage() async { + final sharedPreferences = await SharedPreferences.getInstance(); + return sharedPreferences.getString('token'); } - static Future removeTokenInStorage() async => (await const FlutterSecureStorage().delete(key: 'token')); + static Future saveTokenInStorage(String token) async { + final sharedPreferences = await SharedPreferences.getInstance(); + await sharedPreferences.setString('token', token); + } + + static Future removeTokenInStorage() async { + final sharedPreferences = await SharedPreferences.getInstance(); + await sharedPreferences.remove('token'); + } } abstract class SocketIdStorage { static Future haveSocketIdInStorage() async { + final sharedPreferences = await SharedPreferences.getInstance(); try { - if (await const FlutterSecureStorage().read(key: 'socketId') == null) { + if (sharedPreferences.getString('socketId') == null) { return false; } else { return true; @@ -35,11 +44,18 @@ abstract class SocketIdStorage { } } - static Future getSocketIdFromStorage() async => await const FlutterSecureStorage().read(key: 'socketId'); - - static Future saveSocketIdInStorage(String socketId) async { - await const FlutterSecureStorage().write(key: 'socketId', value: socketId); + static Future getSocketIdFromStorage() async { + final sharedPreferences = await SharedPreferences.getInstance(); + return sharedPreferences.getString('socketId'); } - static Future removeSocketIdInStorage() async => (await const FlutterSecureStorage().delete(key: 'socketId')); + static Future saveSocketIdInStorage(String socketId) async { + final sharedPreferences = await SharedPreferences.getInstance(); + await sharedPreferences.setString('socketId', socketId); + } + + static Future removeSocketIdInStorage() async { + final sharedPreferences = await SharedPreferences.getInstance(); + await sharedPreferences.remove('socketId'); + } } diff --git a/lib/core/widgets/custom_drop_down.dart b/lib/core/widgets/custom_drop_down.dart index aca39b44..9580afe7 100644 --- a/lib/core/widgets/custom_drop_down.dart +++ b/lib/core/widgets/custom_drop_down.dart @@ -2,7 +2,8 @@ import 'package:eatvio_app/core/data/selectionPopupModel/selection_popup_model.d import 'package:flutter/material.dart'; class CustomDropDown extends StatelessWidget { - const CustomDropDown({super.key, + const CustomDropDown({ + super.key, this.alignment, this.width, this.focusNode, @@ -48,12 +49,7 @@ class CustomDropDown extends StatelessWidget { @override Widget build(BuildContext context) { - return alignment != null - ? Align( - alignment: alignment ?? Alignment.center, - child: _buildDropDownWidget(context), - ) - : _buildDropDownWidget(context); + return alignment != null ? Align(alignment: alignment ?? Alignment.center, child: _buildDropDownWidget(context)) : _buildDropDownWidget(context); } DropdownButtonFormField _buildDropDownWidget(BuildContext context) { @@ -61,7 +57,7 @@ class CustomDropDown extends StatelessWidget { focusNode: focusNode ?? FocusNode(), icon: const Icon(Icons.keyboard_arrow_down_rounded), autofocus: autofocus!, - value: value, + initialValue: value, isDense: true, isExpanded: true, items: items?.map((SelectionPopupModel item) { @@ -69,12 +65,7 @@ class CustomDropDown extends StatelessWidget { value: item, child: Text( item.title, - style: hintStyle ?? - const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Colors.black, - ), + style: hintStyle ?? const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black), ), ); }).toList(), @@ -90,19 +81,9 @@ class CustomDropDown extends StatelessWidget { InputDecoration _buildInputDecoration(BuildContext context) { return InputDecoration( - labelStyle: const TextStyle( - fontSize: 16, - color: Colors.black, - fontWeight: FontWeight.w400, - ), + labelStyle: const TextStyle(fontSize: 16, color: Colors.black, fontWeight: FontWeight.w400), hintText: hintText ?? "", - hintStyle: hintStyle ?? - TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Colors.grey.shade400, - height: 1, - ), + hintStyle: hintStyle ?? TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.grey.shade400, height: 1), prefixIcon: prefix, prefixIconConstraints: prefixConstraints, suffixIcon: suffix, @@ -111,18 +92,9 @@ class CustomDropDown extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), fillColor: const Color(0xFFF6F6F6), filled: filled, - border: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(20)), - borderSide: BorderSide.none, - ), - enabledBorder: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(20)), - borderSide: BorderSide.none, - ), - focusedBorder: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(20)), - borderSide: BorderSide.none, - ), + border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none), + enabledBorder: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none), + focusedBorder: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none), ); } } diff --git a/lib/core/widgets/custom_drop_down_button.dart b/lib/core/widgets/custom_drop_down_button.dart index 714382e7..ac8f3c2b 100644 --- a/lib/core/widgets/custom_drop_down_button.dart +++ b/lib/core/widgets/custom_drop_down_button.dart @@ -3,14 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; class CustomDropDownButton extends StatelessWidget { - const CustomDropDownButton({ - super.key, - required this.items, - required this.onChanged, - this.value, - this.hint, - this.hasError = false, - }); + const CustomDropDownButton({super.key, required this.items, required this.onChanged, this.value, this.hint, this.hasError = false}); final T? value; final List> items; final void Function(T?)? onChanged; @@ -20,11 +13,7 @@ class CustomDropDownButton extends StatelessWidget { Widget build(BuildContext context) { return DropdownButtonFormField( hint: hint, - icon: SvgPicture.asset( - 'assets/icons/arrow_down2.svg', - width: 12, - height: 8, - ), + icon: SvgPicture.asset('assets/icons/arrow_down2.svg', width: 12, height: 8), borderRadius: BorderRadius.circular(20), dropdownColor: appTheme.lightGray0, decoration: InputDecoration( @@ -43,7 +32,7 @@ class CustomDropDownButton extends StatelessWidget { fillColor: appTheme.lightGray0, filled: true, ), - value: value, + initialValue: value, items: items, onChanged: onChanged, ); diff --git a/lib/features/auth/presentation/pages/scan_qr_code_screen.dart b/lib/features/auth/presentation/pages/scan_qr_code_screen.dart index 411cb3a5..83296f0e 100644 --- a/lib/features/auth/presentation/pages/scan_qr_code_screen.dart +++ b/lib/features/auth/presentation/pages/scan_qr_code_screen.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/firebase_api.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; @@ -92,6 +93,7 @@ class _ScanQRCodeScreenState extends State { await sl().connectToWebSocket(); await sl().getNotifications(); await sl().connectToWebSocket(); + await sl().registerPushToken(); } catch (e) { rethrow; } diff --git a/lib/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart b/lib/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart index 76b90a26..b8fed20b 100644 --- a/lib/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart +++ b/lib/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart @@ -23,81 +23,105 @@ class _RegistrationAsScreenState extends State { children: [ const Padding( padding: EdgeInsets.symmetric(horizontal: 16), - child: Text( - 'Регистрация в качестве', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w700, - height: 1.1, - ), - ), + child: Text('Регистрация в качестве', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, height: 1.1)), ), const SizedBox(height: 16), - RadioListTile( - value: RegistrationAsType.physical, - groupValue: registrationAsType, - title: const Text( - 'Физического лица', - style: TextStyle( - fontSize: 18, - height: 1, + Column( + children: [ + Row( + children: [ + Radio( + value: RegistrationAsType.physical, + // ignore: deprecated_member_use + groupValue: registrationAsType, + activeColor: appTheme.blueA400, + // ignore: deprecated_member_use + onChanged: (value) { + setState(() { + registrationAsType = value!; + }); + }, + ), + const Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Text('Физического лица', style: TextStyle(fontSize: 18, height: 1)), + ), + ), + ], ), - ), - activeColor: appTheme.blueA400, - onChanged: (value) {}, - contentPadding: const EdgeInsets.symmetric(horizontal: 8), - ), - RadioListTile( - value: RegistrationAsType.selfBusy, - groupValue: registrationAsType, - title: const Text( - 'Самозанятого', - style: TextStyle( - fontSize: 18, - height: 1, + Row( + children: [ + Radio( + value: RegistrationAsType.selfBusy, + // ignore: deprecated_member_use + groupValue: registrationAsType, + activeColor: appTheme.blueA400, + // ignore: deprecated_member_use + onChanged: (value) { + setState(() { + registrationAsType = value!; + }); + }, + ), + const Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Text('Самозанятого', style: TextStyle(fontSize: 18, height: 1)), + ), + ), + ], ), - ), - onChanged: null, - contentPadding: const EdgeInsets.symmetric(horizontal: 8), - ), - RadioListTile( - value: RegistrationAsType.iP, - groupValue: registrationAsType, - title: const Text( - 'Индивидуального предпринимателя', - style: TextStyle( - fontSize: 18, - height: 1, + Row( + children: [ + Radio( + value: RegistrationAsType.iP, + // ignore: deprecated_member_use + groupValue: registrationAsType, + activeColor: appTheme.blueA400, + // ignore: deprecated_member_use + onChanged: (value) { + setState(() { + registrationAsType = value!; + }); + }, + ), + const Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Text('Индивидуального предпринимателя', style: TextStyle(fontSize: 18, height: 1)), + ), + ), + ], ), - ), - onChanged: null, - contentPadding: const EdgeInsets.symmetric(horizontal: 8), - ), - RadioListTile( - value: RegistrationAsType.ooo, - groupValue: registrationAsType, - title: const Text( - 'Общества с ограниченной ответственностью', - style: TextStyle( - fontSize: 18, - height: 1, + Row( + children: [ + Radio( + value: RegistrationAsType.ooo, + // ignore: deprecated_member_use + groupValue: registrationAsType, + activeColor: appTheme.blueA400, + // ignore: deprecated_member_use + onChanged: (value) { + setState(() { + registrationAsType = value!; + }); + }, + ), + const Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Text('Общества с ограниченной ответственностью', style: TextStyle(fontSize: 18, height: 1)), + ), + ), + ], ), - ), - onChanged: null, - contentPadding: const EdgeInsets.symmetric(horizontal: 8), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, - ), - child: _divider(context), + ], ), + Padding(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: _divider(context)), const Padding( padding: EdgeInsets.symmetric(horizontal: 16), - child: Text( - '* На данный момент регистрация доступна только в качестве физического лица. Остальные варианты скоро появятся', - ), + child: Text('* На данный момент регистрация доступна только в качестве физического лица. Остальные варианты скоро появятся'), ), ], ), @@ -108,10 +132,7 @@ class _RegistrationAsScreenState extends State { return Container( height: 0.4, width: MediaQuery.of(context).size.width - 32, - decoration: BoxDecoration( - color: Colors.black26, - borderRadius: BorderRadius.circular(16), - ), + decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(16)), ); } } diff --git a/lib/features/notifications/models/notification_model.dart b/lib/features/notifications/models/notification_model.dart index 31d74e10..55c79e51 100644 --- a/lib/features/notifications/models/notification_model.dart +++ b/lib/features/notifications/models/notification_model.dart @@ -548,13 +548,7 @@ class PlanSubscriptionExpiredNotification implements BaseNotificationModel { @override final DateTime createdAt; - PlanSubscriptionExpiredNotification({ - required this.type, - required this.planName, - required this.customerName, - required this.isRead, - required this.createdAt, - }); + PlanSubscriptionExpiredNotification({required this.type, required this.planName, required this.customerName, required this.isRead, required this.createdAt}); factory PlanSubscriptionExpiredNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { return PlanSubscriptionExpiredNotification( @@ -840,7 +834,937 @@ class UnknownNotification implements BaseNotificationModel { UnknownNotification({required this.type, this.isRead = true, DateTime? createdAt}) : createdAt = createdAt ?? DateTime.fromMillisecondsSinceEpoch(0); } +class DietCancelledNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String dietName; + @override + final DateTime createdAt; + + DietCancelledNotification({required this.type, required this.dietName, required this.isRead, required this.createdAt}); + + factory DietCancelledNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return DietCancelledNotification( + type: type, + dietName: json['diet_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class DietStartedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String dietName; + @override + final DateTime createdAt; + + DietStartedNotification({required this.type, required this.dietName, required this.isRead, required this.createdAt}); + + factory DietStartedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return DietStartedNotification( + type: type, + dietName: json['diet_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class DietExpiredNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String dietName; + @override + final DateTime createdAt; + + DietExpiredNotification({required this.type, required this.dietName, required this.isRead, required this.createdAt}); + + factory DietExpiredNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return DietExpiredNotification( + type: type, + dietName: json['diet_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class DietWillStartedInNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String dietName; + final String startDate; + @override + final DateTime createdAt; + + DietWillStartedInNotification({required this.type, required this.dietName, required this.startDate, required this.isRead, required this.createdAt}); + + factory DietWillStartedInNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return DietWillStartedInNotification( + type: type, + dietName: json['diet_name'] ?? '', + startDate: json['start_date'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class DietCanNotBeStartedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String dietName; + final String reason; + @override + final DateTime createdAt; + + DietCanNotBeStartedNotification({required this.type, required this.dietName, required this.reason, required this.isRead, required this.createdAt}); + + factory DietCanNotBeStartedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return DietCanNotBeStartedNotification( + type: type, + dietName: json['diet_name'] ?? '', + reason: json['reason'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class PersonalDietCreationNeededNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String clientName; + @override + final DateTime createdAt; + + PersonalDietCreationNeededNotification({required this.type, required this.planName, required this.clientName, required this.isRead, required this.createdAt}); + + factory PersonalDietCreationNeededNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return PersonalDietCreationNeededNotification( + type: type, + planName: json['plan_name'] ?? '', + clientName: json['client_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ChosenPlanClientLastDayFinishedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + @override + final DateTime createdAt; + + ChosenPlanClientLastDayFinishedNotification({required this.type, required this.planName, required this.isRead, required this.createdAt}); + + factory ChosenPlanClientLastDayFinishedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ChosenPlanClientLastDayFinishedNotification( + type: type, + planName: json['plan_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ChosenPlanClientLastDayStartedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + @override + final DateTime createdAt; + + ChosenPlanClientLastDayStartedNotification({required this.type, required this.planName, required this.isRead, required this.createdAt}); + + factory ChosenPlanClientLastDayStartedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ChosenPlanClientLastDayStartedNotification( + type: type, + planName: json['plan_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class StayOnTrackToAuthorNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String clientName; + @override + final DateTime createdAt; + + StayOnTrackToAuthorNotification({required this.type, required this.planName, required this.clientName, required this.isRead, required this.createdAt}); + + factory StayOnTrackToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return StayOnTrackToAuthorNotification( + type: type, + planName: json['plan_name'] ?? '', + clientName: json['client_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ClientStartPlanSubscriptionToSubscriberNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + @override + final DateTime createdAt; + + ClientStartPlanSubscriptionToSubscriberNotification({required this.type, required this.planName, required this.isRead, required this.createdAt}); + + factory ClientStartPlanSubscriptionToSubscriberNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ClientStartPlanSubscriptionToSubscriberNotification( + type: type, + planName: json['plan_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ConsultantConfirmPlanSubscriptionToSubscriberNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String consultantName; + @override + final DateTime createdAt; + + ConsultantConfirmPlanSubscriptionToSubscriberNotification({ + required this.type, + required this.planName, + required this.consultantName, + required this.isRead, + required this.createdAt, + }); + + factory ConsultantConfirmPlanSubscriptionToSubscriberNotification.fromJson( + Map json, + NotificationType type, { + bool? isRead, + String? createdAt, + }) { + return ConsultantConfirmPlanSubscriptionToSubscriberNotification( + type: type, + planName: json['plan_name'] ?? '', + consultantName: json['consultant_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ClientPaidPlanSubscriptionToAuthorNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String clientName; + final double amount; + @override + final DateTime createdAt; + + ClientPaidPlanSubscriptionToAuthorNotification({ + required this.type, + required this.planName, + required this.clientName, + required this.amount, + required this.isRead, + required this.createdAt, + }); + + factory ClientPaidPlanSubscriptionToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ClientPaidPlanSubscriptionToAuthorNotification( + type: type, + planName: json['plan_name'] ?? '', + clientName: json['client_name'] ?? '', + amount: (json['amount'] as num?)?.toDouble() ?? 0.0, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ConsultantLeftDiaryCommentNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String consultantName; + final DateTime diaryDate; + @override + final DateTime createdAt; + + ConsultantLeftDiaryCommentNotification({ + required this.type, + required this.planName, + required this.consultantName, + required this.diaryDate, + required this.isRead, + required this.createdAt, + }); + + factory ConsultantLeftDiaryCommentNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ConsultantLeftDiaryCommentNotification( + type: type, + planName: json['plan_name'] ?? '', + consultantName: json['consultant_name'] ?? '', + diaryDate: parseDate(json['diary_date'] ?? DateTime.now().toIso8601String()), + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ConsultantConfirmPlanSubscriptionToAuthorNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String consultantName; + @override + final DateTime createdAt; + + ConsultantConfirmPlanSubscriptionToAuthorNotification({ + required this.type, + required this.planName, + required this.consultantName, + required this.isRead, + required this.createdAt, + }); + + factory ConsultantConfirmPlanSubscriptionToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ConsultantConfirmPlanSubscriptionToAuthorNotification( + type: type, + planName: json['plan_name'] ?? '', + consultantName: json['consultant_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ClientPaidPlanSubscriptionToSubscriberNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final double amount; + @override + final DateTime createdAt; + + ClientPaidPlanSubscriptionToSubscriberNotification({ + required this.type, + required this.planName, + required this.amount, + required this.isRead, + required this.createdAt, + }); + + factory ClientPaidPlanSubscriptionToSubscriberNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ClientPaidPlanSubscriptionToSubscriberNotification( + type: type, + planName: json['plan_name'] ?? '', + amount: (json['amount'] as num?)?.toDouble() ?? 0.0, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ClientStartPlanSubscriptionToAuthorNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String clientName; + @override + final DateTime createdAt; + + ClientStartPlanSubscriptionToAuthorNotification({ + required this.type, + required this.planName, + required this.clientName, + required this.isRead, + required this.createdAt, + }); + + factory ClientStartPlanSubscriptionToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ClientStartPlanSubscriptionToAuthorNotification( + type: type, + planName: json['plan_name'] ?? '', + clientName: json['client_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ClientCancelPlanSubscriptionToAuthorNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String clientName; + @override + final DateTime createdAt; + + ClientCancelPlanSubscriptionToAuthorNotification({ + required this.type, + required this.planName, + required this.clientName, + required this.isRead, + required this.createdAt, + }); + + factory ClientCancelPlanSubscriptionToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ClientCancelPlanSubscriptionToAuthorNotification( + type: type, + planName: json['plan_name'] ?? '', + clientName: json['client_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ClientCancelPlanSubscriptionToSubscriberNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + @override + final DateTime createdAt; + + ClientCancelPlanSubscriptionToSubscriberNotification({required this.type, required this.planName, required this.isRead, required this.createdAt}); + + factory ClientCancelPlanSubscriptionToSubscriberNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ClientCancelPlanSubscriptionToSubscriberNotification( + type: type, + planName: json['plan_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class UserDailyReportCompletedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String clientName; + final DateTime reportDate; + @override + final DateTime createdAt; + + UserDailyReportCompletedNotification({ + required this.type, + required this.planName, + required this.clientName, + required this.reportDate, + required this.isRead, + required this.createdAt, + }); + + factory UserDailyReportCompletedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return UserDailyReportCompletedNotification( + type: type, + planName: json['plan_name'] ?? '', + clientName: json['client_name'] ?? '', + reportDate: parseDate(json['report_date'] ?? DateTime.now().toIso8601String()), + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class StayOnTrackToCustomerNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String consultantName; + @override + final DateTime createdAt; + + StayOnTrackToCustomerNotification({required this.type, required this.planName, required this.consultantName, required this.isRead, required this.createdAt}); + + factory StayOnTrackToCustomerNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return StayOnTrackToCustomerNotification( + type: type, + planName: json['plan_name'] ?? '', + consultantName: json['consultant_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class PhoneChangedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String oldPhone; + final String newPhone; + @override + final DateTime createdAt; + + PhoneChangedNotification({required this.type, required this.oldPhone, required this.newPhone, required this.isRead, required this.createdAt}); + + factory PhoneChangedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return PhoneChangedNotification( + type: type, + oldPhone: json['old_phone'] ?? '', + newPhone: json['new_phone'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class SubscriptionNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String subscriptionType; + final String planName; + @override + final DateTime createdAt; + + SubscriptionNotification({required this.type, required this.subscriptionType, required this.planName, required this.isRead, required this.createdAt}); + + factory SubscriptionNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return SubscriptionNotification( + type: type, + subscriptionType: json['subscription_type'] ?? '', + planName: json['plan_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class BadgeAwardedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String badgeName; + final String badgeDescription; + @override + final DateTime createdAt; + + BadgeAwardedNotification({required this.type, required this.badgeName, required this.badgeDescription, required this.isRead, required this.createdAt}); + + factory BadgeAwardedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return BadgeAwardedNotification( + type: type, + badgeName: json['badge_name'] ?? '', + badgeDescription: json['badge_description'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class BadgeRevokedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String badgeName; + final String reason; + @override + final DateTime createdAt; + + BadgeRevokedNotification({required this.type, required this.badgeName, required this.reason, required this.isRead, required this.createdAt}); + + factory BadgeRevokedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return BadgeRevokedNotification( + type: type, + badgeName: json['badge_name'] ?? '', + reason: json['reason'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ConsultantRequestStartedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String consultantName; + @override + final DateTime createdAt; + + ConsultantRequestStartedNotification({required this.type, required this.consultantName, required this.isRead, required this.createdAt}); + + factory ConsultantRequestStartedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ConsultantRequestStartedNotification( + type: type, + consultantName: json['consultant_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ClientCloseComplaintSuccessNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String complaintType; + final String entityName; + @override + final DateTime createdAt; + + ClientCloseComplaintSuccessNotification({ + required this.type, + required this.complaintType, + required this.entityName, + required this.isRead, + required this.createdAt, + }); + + factory ClientCloseComplaintSuccessNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ClientCloseComplaintSuccessNotification( + type: type, + complaintType: json['complaint_type'] ?? '', + entityName: json['entity_name'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ClientRefundSuccessNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final double amount; + final String reason; + @override + final DateTime createdAt; + + ClientRefundSuccessNotification({ + required this.type, + required this.planName, + required this.amount, + required this.reason, + required this.isRead, + required this.createdAt, + }); + + factory ClientRefundSuccessNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ClientRefundSuccessNotification( + type: type, + planName: json['plan_name'] ?? '', + amount: (json['amount'] as num?)?.toDouble() ?? 0.0, + reason: json['reason'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ConsultantRefundSuccessNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String clientName; + final double amount; + @override + final DateTime createdAt; + + ConsultantRefundSuccessNotification({ + required this.type, + required this.planName, + required this.clientName, + required this.amount, + required this.isRead, + required this.createdAt, + }); + + factory ConsultantRefundSuccessNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ConsultantRefundSuccessNotification( + type: type, + planName: json['plan_name'] ?? '', + clientName: json['client_name'] ?? '', + amount: (json['amount'] as num?)?.toDouble() ?? 0.0, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ClientRejectSuccessNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String reason; + @override + final DateTime createdAt; + + ClientRejectSuccessNotification({required this.type, required this.planName, required this.reason, required this.isRead, required this.createdAt}); + + factory ClientRejectSuccessNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ClientRejectSuccessNotification( + type: type, + planName: json['plan_name'] ?? '', + reason: json['reason'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ConsultantRejectSuccessNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String clientName; + final String reason; + @override + final DateTime createdAt; + + ConsultantRejectSuccessNotification({ + required this.type, + required this.planName, + required this.clientName, + required this.reason, + required this.isRead, + required this.createdAt, + }); + + factory ConsultantRejectSuccessNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ConsultantRejectSuccessNotification( + type: type, + planName: json['plan_name'] ?? '', + clientName: json['client_name'] ?? '', + reason: json['reason'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ScoreSubstractedNotification 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; + + ScoreSubstractedNotification({ + 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 ScoreSubstractedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ScoreSubstractedNotification( + 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 ProductStatusUpdatedForAuthorNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String productName; + final String oldStatus; + final String newStatus; + @override + final DateTime createdAt; + + ProductStatusUpdatedForAuthorNotification({ + required this.type, + required this.productName, + required this.oldStatus, + required this.newStatus, + required this.isRead, + required this.createdAt, + }); + + factory ProductStatusUpdatedForAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ProductStatusUpdatedForAuthorNotification( + type: type, + productName: json['product_name'] ?? '', + oldStatus: json['old_status'] ?? '', + newStatus: json['new_status'] ?? '', + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class DiaryReminderForUsersNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + + DiaryReminderForUsersNotification({required this.type, required this.isRead, required this.createdAt}); + + factory DiaryReminderForUsersNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return DiaryReminderForUsersNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + enum NotificationType { + // Diet related + dietCancelled, + dietStarted, + dietExpired, + dietWillStartedIn, + dietCanNotBeStarted, + + // Plan related + personalDietCreationNeeded, + chosenPlanClientLastDayFinished, + chosenPlanConsultantLastDayStarted, + chosenPlanClientLastDayStarted, + stayOnTrackToAuthor, + chosenPlanStartedToClient, + clientStartPlanSubscriptionToSubscriber, + consultantConfirmPlanSubscriptionToSubscriber, + clientPaidPlanSubscriptionToAuthor, + chosenPlanTomorrowReminderToClient, + chosenPlanTomorrowReminderToConsultant, + chosenPlanStartedToConsultant, + consultantLeftDiaryComment, + chosenPlanFinishedToClient, + consultantConfirmPlanSubscriptionToAuthor, + clientPaidPlanSubscriptionToSubscriber, + clientStartPlanSubscriptionToAuthor, + clientCancelPlanSubscriptionToAuthor, + clientCancelPlanSubscriptionToSubscriber, + userDailyReportCompleted, + chosenPlanFinishedToConsultant, + stayOnTrackToCustomer, + + // Comments + entityCommented, + commentReplied, + + // Profile + phoneChanged, + + // Subscription + subscription, + + // Badges + badgeAwarded, + badgeRevoked, + + // Consultant + consultantRequestStarted, + + // Complaints + clientCloseComplaintSuccess, + complaintToModerationForUser, + + // Finance + clientRefundSuccess, + consultantRefundSuccess, + clientRejectSuccess, + consultantRejectSuccess, + + // Score + scoreAdded, + scoreSubstracted, + + // Product + productStatusUpdatedForAuthor, + + // Diary + diaryReminderForUsers, + + // Legacy types (keeping for backward compatibility) planSubscriptionStartedToAuthor, planSubscriptionCanceledToAuthor, chosenPlanStartedToClientNotification, @@ -848,20 +1772,11 @@ enum NotificationType { diaryReminderNotification, consultantLeftComment, personalizedDietIsDefined, - chosenPlanFinishedToClient, planSubscriptionCanceledToCustomer, chosenPlanStartedToConsultantNotification, chosenPlanTomorrowReminderToConsultantNotification, planSubscriptionPaidToAuthor, - chosenPlanFinishedToConsultant, chosenPlanLastDayFinishedForUser, - // Новые типы - chosenPlanConsultantLastDayStarted, - complaintToModerationForUser, - commentReplied, - scoreAdded, - entityCommented, - // Дополнительные типы уведомлений planSubscriptionExpired, planSubscriptionRenewed, consultantAssigned, @@ -874,10 +1789,57 @@ enum NotificationType { unknown; static NotificationType getTypeFromString(String type) => switch (type) { + // New type-based mapping + 'diet-cancelled' => NotificationType.dietCancelled, + 'entity-commented' => NotificationType.entityCommented, + 'comment-replied' => NotificationType.commentReplied, + 'phone-changed' => NotificationType.phoneChanged, + 'diet-started' => NotificationType.dietStarted, + 'personal-diet-creation-needed-to-author' => NotificationType.personalDietCreationNeeded, + 'chosen-plan-client-last-day-finished' => NotificationType.chosenPlanClientLastDayFinished, + 'chosen-plan-consultant-last-day-started' => NotificationType.chosenPlanConsultantLastDayStarted, + 'chosen-plan-client-last-day-started' => NotificationType.chosenPlanClientLastDayStarted, + 'subscription' => NotificationType.subscription, + 'badge-awarded' => NotificationType.badgeAwarded, + 'badge-revoked' => NotificationType.badgeRevoked, + 'diet-expired' => NotificationType.dietExpired, + 'consultant-request-started' => NotificationType.consultantRequestStarted, + 'diet-will-started-in' => NotificationType.dietWillStartedIn, + 'client-close-complaint-success' => NotificationType.clientCloseComplaintSuccess, + 'complaint-to-moderation-for-user' => NotificationType.complaintToModerationForUser, + 'client-refund-success' => NotificationType.clientRefundSuccess, + 'consultant-refund-success' => NotificationType.consultantRefundSuccess, + 'client-reject-success' => NotificationType.clientRejectSuccess, + 'consultant-reject-success' => NotificationType.consultantRejectSuccess, + 'score-added' => NotificationType.scoreAdded, + 'score-substracted' => NotificationType.scoreSubstracted, + 'product-status-updated-for-author' => NotificationType.productStatusUpdatedForAuthor, + 'diet-can-not-started' => NotificationType.dietCanNotBeStarted, + 'stay-on-track-to-author' => NotificationType.stayOnTrackToAuthor, + 'chosen-plan-started-to-client' => NotificationType.chosenPlanStartedToClient, + 'client-start-plan-subscription-to-subscriber' => NotificationType.clientStartPlanSubscriptionToSubscriber, + 'consultant-confirm-plan-subscription-to-subscriber' => NotificationType.consultantConfirmPlanSubscriptionToSubscriber, + 'client-paid-plan-subscription-to-author' => NotificationType.clientPaidPlanSubscriptionToAuthor, + 'chosen-plan-tomorrow-reminder-to-client' => NotificationType.chosenPlanTomorrowReminderToClient, + 'chosen-plan-tomorrow-reminder-to-consultant' => NotificationType.chosenPlanTomorrowReminderToConsultant, + 'chosen-plan-started-to-consultant' => NotificationType.chosenPlanStartedToConsultant, + 'consultant-left-diary-comment' => NotificationType.consultantLeftDiaryComment, + 'chosen-plan-finished-to-client' => NotificationType.chosenPlanFinishedToClient, + 'consultant-confirm-plan-subscription-to-author' => NotificationType.consultantConfirmPlanSubscriptionToAuthor, + 'client-paid-plan-subscription-to-subscriber' => NotificationType.clientPaidPlanSubscriptionToSubscriber, + 'client-start-plan-subscription-to-author' => NotificationType.clientStartPlanSubscriptionToAuthor, + 'client-cancel-plan-subscription-to-author' => NotificationType.clientCancelPlanSubscriptionToAuthor, + 'client-cancel-plan-subscription-to-subscriber' => NotificationType.clientCancelPlanSubscriptionToSubscriber, + 'user-daily-report-completed' => NotificationType.userDailyReportCompleted, + 'chosen-plan-finished-to-consultant' => NotificationType.chosenPlanFinishedToConsultant, + 'stay-on-track-to-customer' => NotificationType.stayOnTrackToCustomer, + 'diary-reminder-for-users' => NotificationType.diaryReminderForUsers, + + // Legacy mapping '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\\Plan\\ChosenPlanTomorrowReminderToClientNotification' => NotificationType.chosenPlanTomorrowReminderToClientNotification, 'App\\Notifications\\DiaryReminderNotification' => NotificationType.diaryReminderNotification, 'App\\Notifications\\Plan\\ConsultantLeftComment' => NotificationType.consultantLeftComment, 'App\\Notifications\\Plan\\PersonalizedDietIsDefined' => NotificationType.personalizedDietIsDefined, @@ -888,13 +1850,11 @@ enum NotificationType { '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, - // Дополнительные маппинги 'App\\Notifications\\Plan\\PlanSubscriptionExpired' => NotificationType.planSubscriptionExpired, 'App\\Notifications\\Plan\\PlanSubscriptionRenewed' => NotificationType.planSubscriptionRenewed, 'App\\Notifications\\Plan\\ConsultantAssigned' => NotificationType.consultantAssigned, diff --git a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart index df921b4a..8c8c5aaa 100644 --- a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart +++ b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart @@ -66,12 +66,6 @@ class NotificationsCubit extends Cubit { isRead: isRead, createdAt: createdAt, ), - NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson( - notificationJson, - type, - isRead: isRead, - createdAt: createdAt, - ), NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson( notificationJson, type, @@ -96,12 +90,6 @@ class NotificationsCubit extends Cubit { isRead: isRead, createdAt: createdAt, ), - NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson( - notificationJson, - type, - isRead: isRead, - createdAt: createdAt, - ), NotificationType.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.fromJson( notificationJson, type, @@ -123,6 +111,156 @@ class NotificationsCubit extends Cubit { NotificationType.goalAchieved => GoalAchievedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.workoutReminder => WorkoutReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.mealReminder => MealReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + // New notification types + NotificationType.dietCancelled => DietCancelledNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.dietStarted => DietStartedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.dietExpired => DietExpiredNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.dietWillStartedIn => DietWillStartedInNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.dietCanNotBeStarted => DietCanNotBeStartedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.personalDietCreationNeeded => PersonalDietCreationNeededNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.chosenPlanClientLastDayFinished => ChosenPlanClientLastDayFinishedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.chosenPlanClientLastDayStarted => ChosenPlanClientLastDayStartedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.stayOnTrackToAuthor => StayOnTrackToAuthorNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.chosenPlanStartedToClient => ChosenPlanStartedToClientNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.clientStartPlanSubscriptionToSubscriber => ClientStartPlanSubscriptionToSubscriberNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.consultantConfirmPlanSubscriptionToSubscriber => ConsultantConfirmPlanSubscriptionToSubscriberNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.clientPaidPlanSubscriptionToAuthor => ClientPaidPlanSubscriptionToAuthorNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.chosenPlanTomorrowReminderToClient => ChosenPlanTomorrowReminderToClientNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.chosenPlanTomorrowReminderToConsultant => ChosenPlanTomorrowReminderToConsultantNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.chosenPlanStartedToConsultant => ChosenPlanStartedToConsultantNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.consultantLeftDiaryComment => ConsultantLeftDiaryCommentNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.consultantConfirmPlanSubscriptionToAuthor => ConsultantConfirmPlanSubscriptionToAuthorNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.clientPaidPlanSubscriptionToSubscriber => ClientPaidPlanSubscriptionToSubscriberNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.clientStartPlanSubscriptionToAuthor => ClientStartPlanSubscriptionToAuthorNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.clientCancelPlanSubscriptionToAuthor => ClientCancelPlanSubscriptionToAuthorNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.clientCancelPlanSubscriptionToSubscriber => ClientCancelPlanSubscriptionToSubscriberNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.userDailyReportCompleted => UserDailyReportCompletedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.stayOnTrackToCustomer => StayOnTrackToCustomerNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.phoneChanged => PhoneChangedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.subscription => SubscriptionNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.badgeAwarded => BadgeAwardedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.badgeRevoked => BadgeRevokedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.consultantRequestStarted => ConsultantRequestStartedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.clientCloseComplaintSuccess => ClientCloseComplaintSuccessNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.clientRefundSuccess => ClientRefundSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.consultantRefundSuccess => ConsultantRefundSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.clientRejectSuccess => ClientRejectSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.consultantRejectSuccess => ConsultantRejectSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.scoreSubstracted => ScoreSubstractedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.productStatusUpdatedForAuthor => ProductStatusUpdatedForAuthorNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.diaryReminderForUsers => DiaryReminderForUsersNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.unknown => UnknownNotification(type: type), }; } catch (e) { @@ -132,8 +270,36 @@ class NotificationsCubit extends Cubit { void _handleNotification(String notificationData) { final json = jsonDecode(notificationData) as Map; - final stringifiedType = json['notification_class_name']; - final type = NotificationType.getTypeFromString(stringifiedType); + + // Улучшенная логика определения типа уведомления + String? stringifiedType; + + // Сначала проверяем поле 'type' в корне JSON + if (json.containsKey('type') && json['type'] != null) { + stringifiedType = json['type'].toString(); + log('Found type in root: $stringifiedType'); + } + // Затем проверяем поле 'notification_class_name' в корне JSON + else if (json.containsKey('notification_class_name') && json['notification_class_name'] != null) { + stringifiedType = json['notification_class_name'].toString(); + log('Found notification_class_name in root: $stringifiedType'); + } + // Если есть поле 'data', проверяем внутри него + else if (json.containsKey('data') && json['data'] is Map) { + final data = json['data'] as Map; + if (data.containsKey('type') && data['type'] != null) { + stringifiedType = data['type'].toString(); + log('Found type in data: $stringifiedType'); + } else if (data.containsKey('notification_class_name') && data['notification_class_name'] != null) { + stringifiedType = data['notification_class_name'].toString(); + log('Found notification_class_name in data: $stringifiedType'); + } + } + + // Если тип не найден, используем unknown + final type = stringifiedType != null ? NotificationType.getTypeFromString(stringifiedType) : NotificationType.unknown; + log('Final type: $type, stringifiedType: $stringifiedType'); + final notification = _parseNotification(json, type); _notifications.insert(0, notification); diff --git a/lib/features/notifications/presentation/widgets/notification_widget.dart b/lib/features/notifications/presentation/widgets/notification_widget.dart index eff047da..ed196b00 100644 --- a/lib/features/notifications/presentation/widgets/notification_widget.dart +++ b/lib/features/notifications/presentation/widgets/notification_widget.dart @@ -57,6 +57,62 @@ class NotificationWidget extends StatelessWidget { GoalAchievedNotification() => _GoalAchievedWidget(notification: notification as GoalAchievedNotification), WorkoutReminderNotification() => _WorkoutReminderWidget(notification: notification as WorkoutReminderNotification), MealReminderNotification() => _MealReminderWidget(notification: notification as MealReminderNotification), + // New notification types + DietCancelledNotification() => _DietCancelledWidget(notification: notification as DietCancelledNotification), + DietStartedNotification() => _DietStartedWidget(notification: notification as DietStartedNotification), + DietExpiredNotification() => _DietExpiredWidget(notification: notification as DietExpiredNotification), + DietWillStartedInNotification() => _DietWillStartedInWidget(notification: notification as DietWillStartedInNotification), + DietCanNotBeStartedNotification() => _DietCanNotBeStartedWidget(notification: notification as DietCanNotBeStartedNotification), + PersonalDietCreationNeededNotification() => _PersonalDietCreationNeededWidget(notification: notification as PersonalDietCreationNeededNotification), + ChosenPlanClientLastDayFinishedNotification() => _ChosenPlanClientLastDayFinishedWidget( + notification: notification as ChosenPlanClientLastDayFinishedNotification, + ), + ChosenPlanClientLastDayStartedNotification() => _ChosenPlanClientLastDayStartedWidget( + notification: notification as ChosenPlanClientLastDayStartedNotification, + ), + StayOnTrackToAuthorNotification() => _StayOnTrackToAuthorWidget(notification: notification as StayOnTrackToAuthorNotification), + ClientStartPlanSubscriptionToSubscriberNotification() => _ClientStartPlanSubscriptionToSubscriberWidget( + notification: notification as ClientStartPlanSubscriptionToSubscriberNotification, + ), + ConsultantConfirmPlanSubscriptionToSubscriberNotification() => _ConsultantConfirmPlanSubscriptionToSubscriberWidget( + notification: notification as ConsultantConfirmPlanSubscriptionToSubscriberNotification, + ), + ClientPaidPlanSubscriptionToAuthorNotification() => _ClientPaidPlanSubscriptionToAuthorWidget( + notification: notification as ClientPaidPlanSubscriptionToAuthorNotification, + ), + ConsultantLeftDiaryCommentNotification() => _ConsultantLeftDiaryCommentWidget(notification: notification as ConsultantLeftDiaryCommentNotification), + ConsultantConfirmPlanSubscriptionToAuthorNotification() => _ConsultantConfirmPlanSubscriptionToAuthorWidget( + notification: notification as ConsultantConfirmPlanSubscriptionToAuthorNotification, + ), + ClientPaidPlanSubscriptionToSubscriberNotification() => _ClientPaidPlanSubscriptionToSubscriberWidget( + notification: notification as ClientPaidPlanSubscriptionToSubscriberNotification, + ), + ClientStartPlanSubscriptionToAuthorNotification() => _ClientStartPlanSubscriptionToAuthorWidget( + notification: notification as ClientStartPlanSubscriptionToAuthorNotification, + ), + ClientCancelPlanSubscriptionToAuthorNotification() => _ClientCancelPlanSubscriptionToAuthorWidget( + notification: notification as ClientCancelPlanSubscriptionToAuthorNotification, + ), + ClientCancelPlanSubscriptionToSubscriberNotification() => _ClientCancelPlanSubscriptionToSubscriberWidget( + notification: notification as ClientCancelPlanSubscriptionToSubscriberNotification, + ), + UserDailyReportCompletedNotification() => _UserDailyReportCompletedWidget(notification: notification as UserDailyReportCompletedNotification), + StayOnTrackToCustomerNotification() => _StayOnTrackToCustomerWidget(notification: notification as StayOnTrackToCustomerNotification), + PhoneChangedNotification() => _PhoneChangedWidget(notification: notification as PhoneChangedNotification), + SubscriptionNotification() => _SubscriptionWidget(notification: notification as SubscriptionNotification), + BadgeAwardedNotification() => _BadgeAwardedWidget(notification: notification as BadgeAwardedNotification), + BadgeRevokedNotification() => _BadgeRevokedWidget(notification: notification as BadgeRevokedNotification), + ConsultantRequestStartedNotification() => _ConsultantRequestStartedWidget(notification: notification as ConsultantRequestStartedNotification), + ClientCloseComplaintSuccessNotification() => _ClientCloseComplaintSuccessWidget(notification: notification as ClientCloseComplaintSuccessNotification), + ClientRefundSuccessNotification() => _ClientRefundSuccessWidget(notification: notification as ClientRefundSuccessNotification), + ConsultantRefundSuccessNotification() => _ConsultantRefundSuccessWidget(notification: notification as ConsultantRefundSuccessNotification), + ClientRejectSuccessNotification() => _ClientRejectSuccessWidget(notification: notification as ClientRejectSuccessNotification), + ConsultantRejectSuccessNotification() => _ConsultantRejectSuccessWidget(notification: notification as ConsultantRejectSuccessNotification), + ScoreSubstractedNotification() => _ScoreSubstractedWidget(notification: notification as ScoreSubstractedNotification), + ProductStatusUpdatedForAuthorNotification() => _ProductStatusUpdatedForAuthorWidget( + notification: notification as ProductStatusUpdatedForAuthorNotification, + ), + DiaryReminderForUsersNotification() => _DiaryReminderForUsersWidget(notification: notification as DiaryReminderForUsersNotification), UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification), }; } @@ -571,3 +627,429 @@ class _MealReminderWidget extends StatelessWidget { ); } } + +// New notification widget classes +class _DietCancelledWidget extends StatelessWidget { + const _DietCancelledWidget({required this.notification}); + final DietCancelledNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Диета "${notification.dietName}" отменена', + createdAt: notification.createdAt, + ); + } +} + +class _DietStartedWidget extends StatelessWidget { + const _DietStartedWidget({required this.notification}); + final DietStartedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Диета "${notification.dietName}" началась', + createdAt: notification.createdAt, + ); + } +} + +class _DietExpiredWidget extends StatelessWidget { + const _DietExpiredWidget({required this.notification}); + final DietExpiredNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Диета "${notification.dietName}" истекла', + createdAt: notification.createdAt, + ); + } +} + +class _DietWillStartedInWidget extends StatelessWidget { + const _DietWillStartedInWidget({required this.notification}); + final DietWillStartedInNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Диета "${notification.dietName}" начнется ${notification.startDate}', + createdAt: notification.createdAt, + ); + } +} + +class _DietCanNotBeStartedWidget extends StatelessWidget { + const _DietCanNotBeStartedWidget({required this.notification}); + final DietCanNotBeStartedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Диета "${notification.dietName}" не может быть начата: ${notification.reason}', + createdAt: notification.createdAt, + ); + } +} + +class _PersonalDietCreationNeededWidget extends StatelessWidget { + const _PersonalDietCreationNeededWidget({required this.notification}); + final PersonalDietCreationNeededNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Требуется создание персональной диеты для клиента ${notification.clientName} по плану "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _ChosenPlanClientLastDayFinishedWidget extends StatelessWidget { + const _ChosenPlanClientLastDayFinishedWidget({required this.notification}); + final ChosenPlanClientLastDayFinishedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Последний день плана "${notification.planName}" завершен', + createdAt: notification.createdAt, + ); + } +} + +class _ChosenPlanClientLastDayStartedWidget extends StatelessWidget { + const _ChosenPlanClientLastDayStartedWidget({required this.notification}); + final ChosenPlanClientLastDayStartedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Последний день плана "${notification.planName}" начался', + createdAt: notification.createdAt, + ); + } +} + +class _StayOnTrackToAuthorWidget extends StatelessWidget { + const _StayOnTrackToAuthorWidget({required this.notification}); + final StayOnTrackToAuthorNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Клиент ${notification.clientName} остается на плане "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _ClientStartPlanSubscriptionToSubscriberWidget extends StatelessWidget { + const _ClientStartPlanSubscriptionToSubscriberWidget({required this.notification}); + final ClientStartPlanSubscriptionToSubscriberNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Подписка на план "${notification.planName}" началась', + createdAt: notification.createdAt, + ); + } +} + +class _ConsultantConfirmPlanSubscriptionToSubscriberWidget extends StatelessWidget { + const _ConsultantConfirmPlanSubscriptionToSubscriberWidget({required this.notification}); + final ConsultantConfirmPlanSubscriptionToSubscriberNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Консультант ${notification.consultantName} подтвердил подписку на план "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _ClientPaidPlanSubscriptionToAuthorWidget extends StatelessWidget { + const _ClientPaidPlanSubscriptionToAuthorWidget({required this.notification}); + final ClientPaidPlanSubscriptionToAuthorNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Клиент ${notification.clientName} оплатил подписку на план "${notification.planName}" на сумму ${notification.amount}', + createdAt: notification.createdAt, + ); + } +} + +class _ConsultantLeftDiaryCommentWidget extends StatelessWidget { + const _ConsultantLeftDiaryCommentWidget({required this.notification}); + final ConsultantLeftDiaryCommentNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Консультант ${notification.consultantName} оставил комментарий к дневнику по плану "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _ConsultantConfirmPlanSubscriptionToAuthorWidget extends StatelessWidget { + const _ConsultantConfirmPlanSubscriptionToAuthorWidget({required this.notification}); + final ConsultantConfirmPlanSubscriptionToAuthorNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Консультант ${notification.consultantName} подтвердил подписку на план "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _ClientPaidPlanSubscriptionToSubscriberWidget extends StatelessWidget { + const _ClientPaidPlanSubscriptionToSubscriberWidget({required this.notification}); + final ClientPaidPlanSubscriptionToSubscriberNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Оплачена подписка на план "${notification.planName}" на сумму ${notification.amount}', + createdAt: notification.createdAt, + ); + } +} + +class _ClientStartPlanSubscriptionToAuthorWidget extends StatelessWidget { + const _ClientStartPlanSubscriptionToAuthorWidget({required this.notification}); + final ClientStartPlanSubscriptionToAuthorNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Клиент ${notification.clientName} начал подписку на план "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _ClientCancelPlanSubscriptionToAuthorWidget extends StatelessWidget { + const _ClientCancelPlanSubscriptionToAuthorWidget({required this.notification}); + final ClientCancelPlanSubscriptionToAuthorNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Клиент ${notification.clientName} отменил подписку на план "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _ClientCancelPlanSubscriptionToSubscriberWidget extends StatelessWidget { + const _ClientCancelPlanSubscriptionToSubscriberWidget({required this.notification}); + final ClientCancelPlanSubscriptionToSubscriberNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Подписка на план "${notification.planName}" отменена', + createdAt: notification.createdAt, + ); + } +} + +class _UserDailyReportCompletedWidget extends StatelessWidget { + const _UserDailyReportCompletedWidget({required this.notification}); + final UserDailyReportCompletedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Клиент ${notification.clientName} завершил ежедневный отчет по плану "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _StayOnTrackToCustomerWidget extends StatelessWidget { + const _StayOnTrackToCustomerWidget({required this.notification}); + final StayOnTrackToCustomerNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Консультант ${notification.consultantName} остается на плане "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _PhoneChangedWidget extends StatelessWidget { + const _PhoneChangedWidget({required this.notification}); + final PhoneChangedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Номер телефона изменен с ${notification.oldPhone} на ${notification.newPhone}', + createdAt: notification.createdAt, + ); + } +} + +class _SubscriptionWidget extends StatelessWidget { + const _SubscriptionWidget({required this.notification}); + final SubscriptionNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Подписка ${notification.subscriptionType} на план "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _BadgeAwardedWidget extends StatelessWidget { + const _BadgeAwardedWidget({required this.notification}); + final BadgeAwardedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Получен значок "${notification.badgeName}": ${notification.badgeDescription}', + createdAt: notification.createdAt, + ); + } +} + +class _BadgeRevokedWidget extends StatelessWidget { + const _BadgeRevokedWidget({required this.notification}); + final BadgeRevokedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Отозван значок "${notification.badgeName}": ${notification.reason}', + createdAt: notification.createdAt, + ); + } +} + +class _ConsultantRequestStartedWidget extends StatelessWidget { + const _ConsultantRequestStartedWidget({required this.notification}); + final ConsultantRequestStartedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Запрос к консультанту ${notification.consultantName} начат', + createdAt: notification.createdAt, + ); + } +} + +class _ClientCloseComplaintSuccessWidget extends StatelessWidget { + const _ClientCloseComplaintSuccessWidget({required this.notification}); + final ClientCloseComplaintSuccessNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Жалоба на ${notification.complaintType} "${notification.entityName}" успешно закрыта', + createdAt: notification.createdAt, + ); + } +} + +class _ClientRefundSuccessWidget extends StatelessWidget { + const _ClientRefundSuccessWidget({required this.notification}); + final ClientRefundSuccessNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Возврат средств по плану "${notification.planName}" на сумму ${notification.amount}: ${notification.reason}', + createdAt: notification.createdAt, + ); + } +} + +class _ConsultantRefundSuccessWidget extends StatelessWidget { + const _ConsultantRefundSuccessWidget({required this.notification}); + final ConsultantRefundSuccessNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Возврат средств клиенту ${notification.clientName} по плану "${notification.planName}" на сумму ${notification.amount}', + createdAt: notification.createdAt, + ); + } +} + +class _ClientRejectSuccessWidget extends StatelessWidget { + const _ClientRejectSuccessWidget({required this.notification}); + final ClientRejectSuccessNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Отклонение по плану "${notification.planName}": ${notification.reason}', + createdAt: notification.createdAt, + ); + } +} + +class _ConsultantRejectSuccessWidget extends StatelessWidget { + const _ConsultantRejectSuccessWidget({required this.notification}); + final ConsultantRejectSuccessNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Отклонение клиентом ${notification.clientName} по плану "${notification.planName}": ${notification.reason}', + createdAt: notification.createdAt, + ); + } +} + +class _ScoreSubstractedWidget extends StatelessWidget { + const _ScoreSubstractedWidget({required this.notification}); + final ScoreSubstractedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Вычтено ${notification.scoreValue} баллов за ${notification.scorableName}', + createdAt: notification.createdAt, + ); + } +} + +class _ProductStatusUpdatedForAuthorWidget extends StatelessWidget { + const _ProductStatusUpdatedForAuthorWidget({required this.notification}); + final ProductStatusUpdatedForAuthorNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Статус продукта "${notification.productName}" изменен с ${notification.oldStatus} на ${notification.newStatus}', + createdAt: notification.createdAt, + ); + } +} + +class _DiaryReminderForUsersWidget extends StatelessWidget { + const _DiaryReminderForUsersWidget({required this.notification}); + final DiaryReminderForUsersNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Напоминание о заполнении дневника', createdAt: notification.createdAt); + } +} diff --git a/lib/features/notifications/service/notifications_service.dart b/lib/features/notifications/service/notifications_service.dart index 7fdcb4fe..5b3ca78b 100644 --- a/lib/features/notifications/service/notifications_service.dart +++ b/lib/features/notifications/service/notifications_service.dart @@ -13,10 +13,30 @@ class NotificationsService { final notifications = data.map((notificationJson) { try { - final stringifiedType = notificationJson['data']['notification_class_name']; + // Улучшенная логика определения типа уведомления + String? stringifiedType; + + // Сначала проверяем поле 'type' в корне JSON + if (notificationJson.containsKey('type') && notificationJson['type'] != null) { + stringifiedType = notificationJson['type'].toString(); + } + // Затем проверяем поле 'notification_class_name' в корне JSON + else if (notificationJson.containsKey('notification_class_name') && notificationJson['notification_class_name'] != null) { + stringifiedType = notificationJson['notification_class_name'].toString(); + } + // Если есть поле 'data', проверяем внутри него + else if (notificationJson.containsKey('data') && notificationJson['data'] is Map) { + final data = notificationJson['data'] as Map; + if (data.containsKey('type') && data['type'] != null) { + stringifiedType = data['type'].toString(); + } else if (data.containsKey('notification_class_name') && data['notification_class_name'] != null) { + stringifiedType = data['notification_class_name'].toString(); + } + } + final readDate = notificationJson['read_at']; final createdAt = notificationJson['created_at']; - final type = NotificationType.getTypeFromString(stringifiedType); + final type = stringifiedType != null ? NotificationType.getTypeFromString(stringifiedType) : NotificationType.unknown; return switch (type) { NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson( @@ -174,6 +194,219 @@ class NotificationsService { isRead: readDate != null, createdAt: createdAt, ), + // New notification types + NotificationType.dietCancelled => DietCancelledNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.dietStarted => DietStartedNotification.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt), + NotificationType.dietExpired => DietExpiredNotification.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt), + NotificationType.dietWillStartedIn => DietWillStartedInNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.dietCanNotBeStarted => DietCanNotBeStartedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.personalDietCreationNeeded => PersonalDietCreationNeededNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.chosenPlanClientLastDayFinished => ChosenPlanClientLastDayFinishedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.chosenPlanClientLastDayStarted => ChosenPlanClientLastDayStartedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.stayOnTrackToAuthor => StayOnTrackToAuthorNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.chosenPlanStartedToClient => ChosenPlanStartedToClientNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.clientStartPlanSubscriptionToSubscriber => ClientStartPlanSubscriptionToSubscriberNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.consultantConfirmPlanSubscriptionToSubscriber => ConsultantConfirmPlanSubscriptionToSubscriberNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.clientPaidPlanSubscriptionToAuthor => ClientPaidPlanSubscriptionToAuthorNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.chosenPlanTomorrowReminderToClient => ChosenPlanTomorrowReminderToClientNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.chosenPlanTomorrowReminderToConsultant => ChosenPlanTomorrowReminderToConsultantNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.chosenPlanStartedToConsultant => ChosenPlanStartedToConsultantNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.consultantLeftDiaryComment => ConsultantLeftDiaryCommentNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.consultantConfirmPlanSubscriptionToAuthor => ConsultantConfirmPlanSubscriptionToAuthorNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.clientPaidPlanSubscriptionToSubscriber => ClientPaidPlanSubscriptionToSubscriberNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.clientStartPlanSubscriptionToAuthor => ClientStartPlanSubscriptionToAuthorNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.clientCancelPlanSubscriptionToAuthor => ClientCancelPlanSubscriptionToAuthorNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.clientCancelPlanSubscriptionToSubscriber => ClientCancelPlanSubscriptionToSubscriberNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.userDailyReportCompleted => UserDailyReportCompletedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.stayOnTrackToCustomer => StayOnTrackToCustomerNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.phoneChanged => PhoneChangedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.subscription => SubscriptionNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.badgeAwarded => BadgeAwardedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.badgeRevoked => BadgeRevokedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.consultantRequestStarted => ConsultantRequestStartedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.clientCloseComplaintSuccess => ClientCloseComplaintSuccessNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.clientRefundSuccess => ClientRefundSuccessNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.consultantRefundSuccess => ConsultantRefundSuccessNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.clientRejectSuccess => ClientRejectSuccessNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.consultantRejectSuccess => ConsultantRejectSuccessNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.scoreSubstracted => ScoreSubstractedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.productStatusUpdatedForAuthor => ProductStatusUpdatedForAuthorNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.diaryReminderForUsers => DiaryReminderForUsersNotification.fromJson( + notificationJson, + type, + isRead: readDate != null, + createdAt: createdAt, + ), NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)), }; } catch (e) { diff --git a/lib/features/profile/repositories/profile_repository.dart b/lib/features/profile/repositories/profile_repository.dart index 05399457..531cf589 100644 --- a/lib/features/profile/repositories/profile_repository.dart +++ b/lib/features/profile/repositories/profile_repository.dart @@ -12,9 +12,7 @@ import 'package:eatvio_app/features/profile/services/profile_service.dart'; class ProfileRepository { final ProfileService profileService; - ProfileRepository({ - required this.profileService, - }); + ProfileRepository({required this.profileService}); Future getProfileBySlug(String slug) async { try { @@ -48,11 +46,7 @@ class ProfileRepository { } } - Future addCircumference({ - required int chestCircumference, - required int waistCircumference, - required int hipCircumference, - }) async { + Future addCircumference({required int chestCircumference, required int waistCircumference, required int hipCircumference}) async { try { return (await profileService.addCircumference( chestCircumference: chestCircumference, @@ -64,9 +58,7 @@ class ProfileRepository { } } - Future addWeight({ - required double weight, - }) async { + Future addWeight({required double weight}) async { try { return (await profileService.addWeight(weight: weight)); } catch (e) { @@ -74,52 +66,32 @@ class ProfileRepository { } } - Future> getBlogPostsByAuthorSlug({ - required String slug, - }) async { + Future> getBlogPostsByAuthorSlug({required String slug}) async { try { - return (await profileService.getBlogPostByAuthorSlug( - slug: slug, - )); + return (await profileService.getBlogPostByAuthorSlug(slug: slug)); } catch (e) { rethrow; } } - Future> getWeight({ - required String period, - String? slug, - }) async { + Future> getWeight({required String period, String? slug}) async { try { if (slug != null) { - return (await profileService.getWeightBySlug( - period: period, - slug: slug, - )); + return (await profileService.getWeightBySlug(period: period, slug: slug)); } else { - return (await profileService.getWeight( - period: period, - )); + return (await profileService.getWeight(period: period)); } } catch (e) { rethrow; } } - Future> getCircumferences({ - required String period, - String? slug, - }) async { + Future> getCircumferences({required String period, String? slug}) async { try { if (slug != null) { - return (await profileService.getCircumferencesBySlug( - period: period, - slug: slug, - )); + return (await profileService.getCircumferencesBySlug(period: period, slug: slug)); } else { - return (await profileService.getCircumferences( - period: period, - )); + return (await profileService.getCircumferences(period: period)); } } catch (e) { rethrow; @@ -184,7 +156,8 @@ class ProfileRepository { Future getPassKey(String phone) async { return (jsonDecode( - (await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}'))))['key_is']!; + (await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}')), + ))['key_is']!; } Future getMyPhone() async { @@ -206,7 +179,7 @@ class ProfileRepository { Future deleteAccount() async { try { - await profileService.deleteAccount(); + await profileService.deleteAccount(); } catch (e) { rethrow; } diff --git a/lib/features/profile/services/profile_service.dart b/lib/features/profile/services/profile_service.dart index a1208526..bddd46f8 100644 --- a/lib/features/profile/services/profile_service.dart +++ b/lib/features/profile/services/profile_service.dart @@ -64,7 +64,7 @@ abstract class ProfileService { @Path() required String period, }); - @GET('/api/profile/measure/circumferences/{period}/{user}') + @GET('/api/profile/measure/circumferences/{period}/{slug}') Future> getCircumferencesBySlug({ @Path() required String period, @Path() required String slug, diff --git a/lib/features/registration/presentation/pages/gender_screen.dart b/lib/features/registration/presentation/pages/gender_screen.dart index 4403b54e..e58df3d4 100644 --- a/lib/features/registration/presentation/pages/gender_screen.dart +++ b/lib/features/registration/presentation/pages/gender_screen.dart @@ -27,48 +27,46 @@ class _GenderScreenState extends State { padding: const EdgeInsets.only(bottom: 80, top: 16, left: 16, right: 16), child: Column( children: [ - RadioListTile( - contentPadding: EdgeInsets.zero, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - title: const Text('Мужской'), - value: 'male', - groupValue: currentValue, - onChanged: (newValue) { - final registrationCubit = context.read(); - registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(gender: 'male')); - setState(() { - currentValue = 'male'; - }); - }, - secondary: Image.asset( - 'assets/images/img_male.png', - width: 40, - height: 40, - ), + Row( + children: [ + Radio( + value: 'male', + // ignore: deprecated_member_use + groupValue: currentValue, + // ignore: deprecated_member_use + onChanged: (newValue) { + final registrationCubit = context.read(); + registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(gender: 'male')); + setState(() { + currentValue = 'male'; + }); + }, + ), + Expanded( + child: Row(children: [Image.asset('assets/images/img_male.png', width: 40, height: 40), const SizedBox(width: 8), const Text('Мужской')]), + ), + ], ), const SizedBox(height: 16), - RadioListTile( - contentPadding: EdgeInsets.zero, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - title: const Text('Женский'), - value: 'female', - groupValue: currentValue, - onChanged: (newValue) { - final registrationCubit = context.read(); - registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(gender: 'female')); - setState(() { - currentValue = 'female'; - }); - }, - secondary: Image.asset( - 'assets/images/img_female.png', - width: 40, - height: 40, - ), + Row( + children: [ + Radio( + value: 'female', + // ignore: deprecated_member_use + groupValue: currentValue, + // ignore: deprecated_member_use + onChanged: (newValue) { + final registrationCubit = context.read(); + registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(gender: 'female')); + setState(() { + currentValue = 'female'; + }); + }, + ), + Expanded( + child: Row(children: [Image.asset('assets/images/img_female.png', width: 40, height: 40), const SizedBox(width: 8), const Text('Женский')]), + ), + ], ), ], ), diff --git a/lib/features/settings/presentation/pages/physical_activity_screen.dart b/lib/features/settings/presentation/pages/physical_activity_screen.dart index 3c6b000c..df91a4f6 100644 --- a/lib/features/settings/presentation/pages/physical_activity_screen.dart +++ b/lib/features/settings/presentation/pages/physical_activity_screen.dart @@ -7,10 +7,7 @@ import 'package:go_router/go_router.dart'; class PhysicalActivityScreen extends StatefulWidget { final SettingsEntity settingsEntity; - const PhysicalActivityScreen({ - super.key, - required this.settingsEntity, - }); + const PhysicalActivityScreen({super.key, required this.settingsEntity}); @override State createState() => _PhysicalActivityScreenState(); @@ -46,69 +43,108 @@ class _PhysicalActivityScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Физическая нагрузка'), - ), + appBar: AppBar(title: const Text('Физическая нагрузка')), bottomSheet: _saveButton(), body: SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.only(top: 16, bottom: 80), child: Column( children: [ - RadioListTile( - contentPadding: const EdgeInsets.only(left: 8, right: 16), - title: const Text('1,2 — минимальный (сидячая работа, отсутствие физических нагрузок);'), - value: PhysicalActivity.minimum, - groupValue: _physicalActivity, - onChanged: (PhysicalActivity? value) { - setState(() { - _physicalActivity = value; - }); - }, + Row( + children: [ + Radio( + value: PhysicalActivity.minimum, + // ignore: deprecated_member_use + groupValue: _physicalActivity, + // ignore: deprecated_member_use + onChanged: (PhysicalActivity? value) { + setState(() { + _physicalActivity = value; + }); + }, + ), + const Expanded( + child: Padding( + padding: EdgeInsets.only(left: 8, right: 16), + child: Text('1,2 — минимальный (сидячая работа, отсутствие физических нагрузок);'), + ), + ), + ], ), - RadioListTile( - contentPadding: const EdgeInsets.only(left: 8, right: 16), - title: const Text('1,375 — низкий (тренировки не менее 20 мин 1–3 раза в неделю);'), - value: PhysicalActivity.low, - groupValue: _physicalActivity, - onChanged: (PhysicalActivity? value) { - setState(() { - _physicalActivity = value; - }); - }, + Row( + children: [ + Radio( + value: PhysicalActivity.low, + // ignore: deprecated_member_use + groupValue: _physicalActivity, + // ignore: deprecated_member_use + onChanged: (PhysicalActivity? value) { + setState(() { + _physicalActivity = value; + }); + }, + ), + const Expanded( + child: Padding(padding: EdgeInsets.only(left: 8, right: 16), child: Text('1,375 — низкий (тренировки не менее 20 мин 1–3 раза в неделю);')), + ), + ], ), - RadioListTile( - contentPadding: const EdgeInsets.only(left: 8, right: 16), - title: const Text('1,55 — умеренный (тренировки 30–60 мин 3–4 раза в неделю);'), - value: PhysicalActivity.medium, - groupValue: _physicalActivity, - onChanged: (PhysicalActivity? value) { - setState(() { - _physicalActivity = value; - }); - }, + Row( + children: [ + Radio( + value: PhysicalActivity.medium, + // ignore: deprecated_member_use + groupValue: _physicalActivity, + // ignore: deprecated_member_use + onChanged: (PhysicalActivity? value) { + setState(() { + _physicalActivity = value; + }); + }, + ), + const Expanded( + child: Padding(padding: EdgeInsets.only(left: 8, right: 16), child: Text('1,55 — умеренный (тренировки 30–60 мин 3–4 раза в неделю);')), + ), + ], ), - RadioListTile( - contentPadding: const EdgeInsets.only(left: 8, right: 16), - title: const Text('1,7 — высокий (тренировки 30–60 мин 5–7 раза в неделю; тяжёлая физическая работа);'), - value: PhysicalActivity.high, - groupValue: _physicalActivity, - onChanged: (PhysicalActivity? value) { - setState(() { - _physicalActivity = value; - }); - }, + Row( + children: [ + Radio( + value: PhysicalActivity.high, + // ignore: deprecated_member_use + groupValue: _physicalActivity, + // ignore: deprecated_member_use + onChanged: (PhysicalActivity? value) { + setState(() { + _physicalActivity = value; + }); + }, + ), + const Expanded( + child: Padding( + padding: EdgeInsets.only(left: 8, right: 16), + child: Text('1,7 — высокий (тренировки 30–60 мин 5–7 раза в неделю; тяжёлая физическая работа);'), + ), + ), + ], ), - RadioListTile( - contentPadding: const EdgeInsets.only(left: 8, right: 16), - title: const Text('1,9 — экстремальный (несколько интенсивных тренировок в день'), - value: PhysicalActivity.extreme, - groupValue: _physicalActivity, - onChanged: (PhysicalActivity? value) { - setState(() { - _physicalActivity = value; - }); - }, + Row( + children: [ + Radio( + value: PhysicalActivity.extreme, + // ignore: deprecated_member_use + groupValue: _physicalActivity, + // ignore: deprecated_member_use + onChanged: (PhysicalActivity? value) { + setState(() { + _physicalActivity = value; + }); + }, + ), + const Expanded( + child: Padding(padding: EdgeInsets.only(left: 8, right: 16), child: Text('1,9 — экстремальный (несколько интенсивных тренировок в день')), + ), + ], ), ], ), @@ -141,8 +177,8 @@ class _PhysicalActivityScreenState extends State { } widget.settingsEntity.settingsCubit.updateProfileDetails( profileDetailsModel: (widget.settingsEntity.settingsCubit.state as SettingsStateLoaded).profileDetails.copyWith( - activityCoefficient: activityCoefficient, - ), + activityCoefficient: activityCoefficient, + ), ); GoRouter.of(context).pop(); }, @@ -150,19 +186,11 @@ class _PhysicalActivityScreenState extends State { child: Ink( height: 52, width: MediaQuery.of(context).size.width - 32, - 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, - height: 1, - fontWeight: FontWeight.w600, - fontSize: 18, - ), + style: TextStyle(color: Colors.white, height: 1, fontWeight: FontWeight.w600, fontSize: 18), ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index d9dda30d..3b33303b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.2.9+69 +version: 1.2.10+70 publish_to: 'none' environment: @@ -13,11 +13,6 @@ dependencies: flutter_localizations: sdk: flutter - flutter_secure_storage: - git: - url: https://github.com/sherlockvn/flutter_secure_storage.git - ref: remove-old-encrypt-algorithm - path: flutter_secure_storage flutter_bloc: ^9.1.1 cached_network_image: ^3.4.1 flutter_svg: ^2.2.0 @@ -27,28 +22,28 @@ dependencies: validators: ^3.0.0 path_provider: ^2.1.5 carousel_slider_plus: ^7.1.1 - flutter_form_builder: ^10.1.0 - image_picker: ^1.1.2 + flutter_form_builder: ^10.2.0 + image_picker: ^1.2.0 flutter_typeahead: ^5.2.0 - go_router: ^16.0.0 + go_router: ^16.1.0 keyboard_dismisser: ^3.0.0 - retrofit: ^4.7.0 - get_it: ^8.1.0 - dio: ^5.8.0+1 + retrofit: ^4.7.1 + get_it: ^8.2.0 + dio: ^5.9.0 dart_pusher_channels: ^1.2.3 photo_view: ^0.15.0 dismissible_page: ^1.0.2 url_launcher: ^6.3.2 easy_debounce: ^2.0.3 badges: ^3.1.2 - google_fonts: ^6.2.1 - syncfusion_flutter_charts: ^30.1.42 - syncfusion_flutter_gauges: ^30.1.42 + google_fonts: ^6.3.0 + syncfusion_flutter_charts: ^30.2.5 + syncfusion_flutter_gauges: ^30.2.5 table_calendar: ^3.2.0 flutter_keyboard_visibility: ^6.0.0 - share_plus: ^11.0.0 + share_plus: ^11.1.0 flutter_html: ^3.0.0 - file_picker: ^10.2.0 + file_picker: ^10.3.1 audioplayers: ^6.5.0 intl: ^0.20.2 mask_text_input_formatter: ^2.9.0 @@ -59,31 +54,31 @@ dependencies: flutter_image_compress: ^2.4.0 image: ^4.5.4 dotted_border: ^3.1.0 - http: ^1.4.0 + http: ^1.5.0 flutter_native_splash: ^2.4.6 webview_flutter: ^4.13.0 - sentry_flutter: ^9.5.0 - connectivity_plus: ^6.1.4 + sentry_flutter: ^9.6.0 + connectivity_plus: ^6.1.5 visibility_detector: ^0.4.0+2 - firebase_core: ^3.15.2 - firebase_messaging: ^15.2.10 + firebase_core: ^4.0.0 + firebase_messaging: ^16.0.0 http_parser: ^4.1.2 path: ^1.9.1 collection: ^1.19.1 shared_preferences: ^2.5.3 version: ^3.0.2 - package_info_plus: ^8.3.0 + package_info_plus: ^8.3.1 device_info_plus: ^11.5.0 permission_handler: ^12.0.1 qr_code_scanner_plus: ^2.0.10+1 - flutter_dotenv: ^5.1.0 + flutter_dotenv: ^5.2.1 uuid: ^4.5.1 dev_dependencies: - build_runner: ^2.6.0 + build_runner: ^2.7.0 freezed: ^3.2.0 json_serializable: ^6.10.0 - retrofit_generator: ^10.0.1 + retrofit_generator: ^10.0.2 mockito: ^5.5.0 flutter_test: