forked from eatvio/eatvio_app
feat(notifications): Issue-236
This commit is contained in:
@@ -24,6 +24,7 @@ import 'package:eatvio_app/features/consultations/presentation/pages/consultatio
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/blocked_users_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/notifications_settings_screen.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
|
||||
@@ -111,23 +112,13 @@ class AppRouter {
|
||||
name: 'authScreen',
|
||||
builder: (context, state) => const AuthScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'registration-screen',
|
||||
name: 'registrationScreen',
|
||||
builder: (context, state) => const RegistrationScreen(),
|
||||
),
|
||||
GoRoute(path: 'registration-screen', name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
|
||||
GoRoute(
|
||||
path: 'forget-password-screen',
|
||||
name: 'forgetPasswordScreen',
|
||||
builder: (context, state) => ForgetPasswordScreen(
|
||||
isAuth: state.extra as bool,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'scan-qr-code-screen',
|
||||
name: 'scanQRCodeScreen',
|
||||
builder: (context, state) => const ScanQRCodeScreen(),
|
||||
builder: (context, state) => ForgetPasswordScreen(isAuth: state.extra as bool),
|
||||
),
|
||||
GoRoute(path: 'scan-qr-code-screen', name: 'scanQRCodeScreen', builder: (context, state) => const ScanQRCodeScreen()),
|
||||
],
|
||||
),
|
||||
ShellRoute(
|
||||
@@ -165,19 +156,23 @@ class AppRouter {
|
||||
GoRoute(
|
||||
path: '/diary-screen',
|
||||
name: 'diaryScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: DiaryScreen(),
|
||||
),
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: ConsultationsScreen.path,
|
||||
name: ConsultationsScreen.name,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen())),
|
||||
GoRoute(path: '/chat-select-screen', name: 'chatSelectScreen', pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen())),
|
||||
path: ConsultationsScreen.path,
|
||||
name: ConsultationsScreen.name,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/notifications-screen',
|
||||
name: 'notificationsScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen())),
|
||||
path: '/chat-select-screen',
|
||||
name: 'chatSelectScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/notifications-screen',
|
||||
name: 'notificationsScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/menu-screen',
|
||||
name: 'menuScreen',
|
||||
@@ -194,11 +189,7 @@ class AppRouter {
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
name: 'blockedUsersScreen',
|
||||
path: '/blocked-users-screen',
|
||||
builder: (context, state) => const BlockedUsersScreen(),
|
||||
),
|
||||
GoRoute(name: 'blockedUsersScreen', path: '/blocked-users-screen', builder: (context, state) => const BlockedUsersScreen()),
|
||||
GoRoute(
|
||||
path: '/welcome-screen',
|
||||
name: 'welcomeScreen',
|
||||
@@ -241,26 +232,10 @@ class AppRouter {
|
||||
}
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: ConsultationsArchiveScreen.path,
|
||||
name: ConsultationsArchiveScreen.name,
|
||||
builder: (context, state) => const ConsultationsArchiveScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: ConsultantFinancesScreen.path,
|
||||
name: ConsultantFinancesScreen.name,
|
||||
builder: (context, state) => const ConsultantFinancesScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: UserPurchasesScreen.path,
|
||||
name: UserPurchasesScreen.name,
|
||||
builder: (context, state) => const UserPurchasesScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/user-finance-screen',
|
||||
name: 'userFinanceScreen',
|
||||
builder: (context, state) => const UserFinanceScreen(),
|
||||
),
|
||||
GoRoute(path: ConsultationsArchiveScreen.path, name: ConsultationsArchiveScreen.name, builder: (context, state) => const ConsultationsArchiveScreen()),
|
||||
GoRoute(path: ConsultantFinancesScreen.path, name: ConsultantFinancesScreen.name, builder: (context, state) => const ConsultantFinancesScreen()),
|
||||
GoRoute(path: UserPurchasesScreen.path, name: UserPurchasesScreen.name, builder: (context, state) => const UserPurchasesScreen()),
|
||||
GoRoute(path: '/user-finance-screen', name: 'userFinanceScreen', builder: (context, state) => const UserFinanceScreen()),
|
||||
GoRoute(
|
||||
path: CardSettingsScreen.path,
|
||||
name: CardSettingsScreen.name,
|
||||
@@ -275,11 +250,7 @@ class AppRouter {
|
||||
builder: (context, state) {
|
||||
final slug = state.uri.queryParameters['slug'];
|
||||
if (slug != null) {
|
||||
return AboutPlanScreen(
|
||||
slug: slug,
|
||||
navigateToComments: false,
|
||||
index: null,
|
||||
);
|
||||
return AboutPlanScreen(slug: slug, navigateToComments: false, index: null);
|
||||
}
|
||||
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
@@ -293,29 +264,23 @@ class AppRouter {
|
||||
builder: (context, state) {
|
||||
final slug = state.uri.queryParameters['slug'];
|
||||
if (slug != null) {
|
||||
return AboutRecipeScreen(
|
||||
recipeSlug: slug,
|
||||
navigateToComments: false,
|
||||
index: null,
|
||||
);
|
||||
return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null);
|
||||
}
|
||||
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutRecipeScreen(
|
||||
recipeSlug: data['recipe'] as String,
|
||||
index: data['index'] as int?,
|
||||
navigateToComments: data['navigateToComments'] as bool,
|
||||
);
|
||||
return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RecipeCreateScreen.path,
|
||||
name: RecipeCreateScreen.name,
|
||||
builder: (context, state) => RecipeCreateScreen(
|
||||
recipe: state.extra != null ? state.extra as Recipe : null,
|
||||
),
|
||||
builder: (context, state) => RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/profile-screen',
|
||||
name: 'profileScreen',
|
||||
builder: (context, state) => ProfileScreen(author: state.extra as Author),
|
||||
),
|
||||
GoRoute(path: '/profile-screen', name: 'profileScreen', builder: (context, state) => ProfileScreen(author: state.extra as Author)),
|
||||
GoRoute(
|
||||
path: '/subs-screen/:slug',
|
||||
name: 'subsScreen',
|
||||
@@ -327,11 +292,7 @@ class AppRouter {
|
||||
builder: (context, state) {
|
||||
final slug = state.uri.queryParameters['slug'];
|
||||
if (slug != null) {
|
||||
return AboutDietScreen(
|
||||
slug: slug,
|
||||
index: null,
|
||||
navigateToComments: false,
|
||||
);
|
||||
return AboutDietScreen(slug: slug, index: null, navigateToComments: false);
|
||||
}
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutDietScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||
@@ -343,19 +304,18 @@ class AppRouter {
|
||||
builder: (context, state) => BlogPostCreateScreen(feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/blog-post-screen',
|
||||
name: 'blogPostScreen',
|
||||
builder: (context, state) {
|
||||
final id = state.uri.queryParameters['id'];
|
||||
final parsedId = int.tryParse(id ?? '');
|
||||
if (parsedId != null) {
|
||||
return BlogPostScreen(
|
||||
id: parsedId,
|
||||
);
|
||||
}
|
||||
path: '/blog-post-screen',
|
||||
name: 'blogPostScreen',
|
||||
builder: (context, state) {
|
||||
final id = state.uri.queryParameters['id'];
|
||||
final parsedId = int.tryParse(id ?? '');
|
||||
if (parsedId != null) {
|
||||
return BlogPostScreen(id: parsedId);
|
||||
}
|
||||
|
||||
return BlogPostScreen(id: state.extra as int);
|
||||
}),
|
||||
return BlogPostScreen(id: state.extra as int);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat-screen',
|
||||
name: 'chatScreen',
|
||||
@@ -363,10 +323,7 @@ class AppRouter {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final uuid = data['uuid'] as String;
|
||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||
return ChatScreen(
|
||||
uuid: uuid,
|
||||
sendInitialMessage: sendInitialMessage,
|
||||
);
|
||||
return ChatScreen(uuid: uuid, sendInitialMessage: sendInitialMessage);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
@@ -399,7 +356,11 @@ class AppRouter {
|
||||
name: 'physicalFormScreen',
|
||||
builder: (context, state) => PhysicalFormScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(path: 'pressure-screen', name: 'pressureScreen', builder: (context, state) => PressureScreen(settingsEntity: state.extra as SettingsEntity)),
|
||||
GoRoute(
|
||||
path: 'pressure-screen',
|
||||
name: 'pressureScreen',
|
||||
builder: (context, state) => PressureScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'change-password-screen',
|
||||
name: 'changePasswordScreen',
|
||||
@@ -416,9 +377,7 @@ class AppRouter {
|
||||
GoRoute(
|
||||
path: '/stepper-become-consultant-screen',
|
||||
name: 'stepperBecomeConsultantScreen',
|
||||
builder: (context, state) => StepperBecomeConsultantScreen(
|
||||
slug: state.extra == null ? null : state.extra as String,
|
||||
),
|
||||
builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String),
|
||||
),
|
||||
GoRoute(path: '/calendar-screen', name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
||||
GoRoute(path: '/support-screen', name: 'supportScreen', builder: (context, state) => const SupportScreen()),
|
||||
@@ -446,7 +405,11 @@ class AppRouter {
|
||||
GoRoute(path: CreatePlanScreen.path, name: CreatePlanScreen.name, builder: (context, state) => const CreatePlanScreen()),
|
||||
GoRoute(path: MyDietsScreen.path, name: MyDietsScreen.name, builder: (context, state) => const MyDietsScreen()),
|
||||
GoRoute(path: MyPlansScreen.path, name: MyPlansScreen.name, builder: (context, state) => const MyPlansScreen()),
|
||||
GoRoute(path: UserDetailsScreen.path, name: UserDetailsScreen.name, builder: (context, state) => UserDetailsScreen(slug: state.extra as String)),
|
||||
GoRoute(
|
||||
path: UserDetailsScreen.path,
|
||||
name: UserDetailsScreen.name,
|
||||
builder: (context, state) => UserDetailsScreen(slug: state.extra as String),
|
||||
),
|
||||
GoRoute(path: DevToolsScreen.path, name: DevToolsScreen.name, builder: (context, state) => const DevToolsScreen()),
|
||||
GoRoute(path: ConsultantRatingScreen.path, name: ConsultantRatingScreen.name, builder: (context, state) => const ConsultantRatingScreen()),
|
||||
GoRoute(
|
||||
@@ -456,10 +419,7 @@ class AppRouter {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final planId = data['planId'] as int;
|
||||
final successfulPayment = data['successfulPayment'] as bool?;
|
||||
return InfoByOrderScreen(
|
||||
planId: planId,
|
||||
successfulPayment: successfulPayment,
|
||||
);
|
||||
return InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
@@ -469,10 +429,7 @@ class AppRouter {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final planId = data['planId'] as int;
|
||||
final url = data['url'] as String;
|
||||
return PaymentScreen(
|
||||
url: url,
|
||||
planId: planId,
|
||||
);
|
||||
return PaymentScreen(url: url, planId: planId);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
@@ -482,10 +439,7 @@ class AppRouter {
|
||||
final data = state.extra as Map<String, int>;
|
||||
final diaryId = data['diaryId'] as int;
|
||||
final currentPlanDay = data['currentDiaryDay'] as int;
|
||||
return UserDiaryScreen(
|
||||
diaryId: diaryId,
|
||||
currentPlanDay: currentPlanDay,
|
||||
);
|
||||
return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
@@ -493,16 +447,9 @@ class AppRouter {
|
||||
name: 'verifyScreen',
|
||||
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/verify-email-screen',
|
||||
name: 'verifyEmailScreen',
|
||||
builder: (context, state) => const VerifyEmailScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: MyRecipesScreen.path,
|
||||
name: MyRecipesScreen.name,
|
||||
builder: (context, state) => const MyRecipesScreen(),
|
||||
),
|
||||
GoRoute(path: '/verify-email-screen', name: 'verifyEmailScreen', builder: (context, state) => const VerifyEmailScreen()),
|
||||
GoRoute(path: MyRecipesScreen.path, name: MyRecipesScreen.name, builder: (context, state) => const MyRecipesScreen()),
|
||||
GoRoute(path: '/notifications-settings-screen', name: 'notificationsSettingsScreen', builder: (context, state) => const NotificationsSettingsScreen()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,8 +43,11 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
|
||||
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
|
||||
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
||||
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
|
||||
@@ -136,14 +139,16 @@ Future<void> init() async {
|
||||
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
||||
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl()));
|
||||
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
||||
sl.registerFactory<DiaryCubit>(() => DiaryCubit(
|
||||
chatRepository: sl(),
|
||||
diaryRepository: sl(),
|
||||
glossaryRepository: sl(),
|
||||
profileRepository: sl(),
|
||||
dietRepository: sl(),
|
||||
settingsRepository: sl(),
|
||||
));
|
||||
sl.registerFactory<DiaryCubit>(
|
||||
() => DiaryCubit(
|
||||
chatRepository: sl(),
|
||||
diaryRepository: sl(),
|
||||
glossaryRepository: sl(),
|
||||
profileRepository: sl(),
|
||||
dietRepository: sl(),
|
||||
settingsRepository: sl(),
|
||||
),
|
||||
);
|
||||
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
|
||||
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
||||
@@ -172,6 +177,7 @@ Future<void> init() async {
|
||||
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
||||
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
||||
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
|
||||
sl.registerFactory<NotificationSettingsCubit>(() => NotificationSettingsCubit(notificationSettingsRepository: sl()));
|
||||
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(sl()));
|
||||
|
||||
// Repository
|
||||
@@ -204,6 +210,7 @@ Future<void> init() async {
|
||||
sl.registerFactory<ResetPasswordRepository>(() => ResetPasswordRepository(resetPasswordService: sl()));
|
||||
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
|
||||
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
|
||||
sl.registerLazySingleton<NotificationSettingsRepository>(() => NotificationSettingsRepository(sl()));
|
||||
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
|
||||
|
||||
// Services
|
||||
@@ -239,22 +246,12 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
|
||||
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
|
||||
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
|
||||
sl.registerLazySingleton<NotificationSettingsService>(() => NotificationSettingsService(sl()));
|
||||
|
||||
// External
|
||||
sl.registerLazySingleton(
|
||||
() => Dio(
|
||||
BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
),
|
||||
)..interceptors.addAll(
|
||||
[
|
||||
TokenInterceptor(),
|
||||
PhoneVerifyErrorInterceptor(),
|
||||
],
|
||||
),
|
||||
() =>
|
||||
Dio(BaseOptions(baseUrl: baseUrl, headers: {'accept': 'application/json', 'content-type': 'application/json'}))
|
||||
..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]),
|
||||
);
|
||||
}
|
||||
|
||||
39
lib/features/menu/models/notification_settings_model.dart
Normal file
39
lib/features/menu/models/notification_settings_model.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'notification_settings_model.g.dart';
|
||||
part 'notification_settings_model.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class NotificationSettingsModel with _$NotificationSettingsModel {
|
||||
const factory NotificationSettingsModel({
|
||||
@JsonKey(name: 'new_updates_notification') required bool newUpdatesNotification,
|
||||
@JsonKey(name: 'weight_measurements_notification') required bool weightMeasurementsNotification,
|
||||
@JsonKey(name: 'is_email_notifications_enabled') required bool isEmailNotificationsEnabled,
|
||||
@JsonKey(name: 'is_push_notifications_enabled') required bool isPushNotificationsEnabled,
|
||||
@JsonKey(name: 'diary_reminder_notifications') required DiaryReminderNotifications diaryReminderNotifications,
|
||||
}) = _NotificationSettingsModel;
|
||||
|
||||
factory NotificationSettingsModel.fromJson(Map<String, Object?> json) => _$NotificationSettingsModelFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class DiaryReminderNotifications with _$DiaryReminderNotifications {
|
||||
const factory DiaryReminderNotifications({
|
||||
@JsonKey(name: 'first_reminder') required ReminderSettings firstReminder,
|
||||
@JsonKey(name: 'second_reminder') required ReminderSettings secondReminder,
|
||||
@JsonKey(name: 'third_reminder') required ReminderSettings thirdReminder,
|
||||
}) = _DiaryReminderNotifications;
|
||||
|
||||
factory DiaryReminderNotifications.fromJson(Map<String, Object?> json) => _$DiaryReminderNotificationsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ReminderSettings with _$ReminderSettings {
|
||||
const factory ReminderSettings({
|
||||
required bool enabled,
|
||||
@JsonKey(name: 'local_time_hours') required int localTimeHours,
|
||||
@JsonKey(name: 'local_time_mins') required int localTimeMins,
|
||||
}) = _ReminderSettings;
|
||||
|
||||
factory ReminderSettings.fromJson(Map<String, Object?> json) => _$ReminderSettingsFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'dart:developer';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart';
|
||||
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
|
||||
|
||||
class NotificationSettingsCubit extends Cubit<NotificationSettingsState> {
|
||||
final NotificationSettingsRepository _notificationSettingsRepository;
|
||||
|
||||
NotificationSettingsCubit({required NotificationSettingsRepository notificationSettingsRepository})
|
||||
: _notificationSettingsRepository = notificationSettingsRepository,
|
||||
super(const NotificationSettingsStateLoading());
|
||||
|
||||
Future<void> getNotificationSettings() async {
|
||||
emit(const NotificationSettingsStateLoading());
|
||||
try {
|
||||
final notificationSettings = await _notificationSettingsRepository.getNotificationSettings();
|
||||
emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings));
|
||||
} catch (e, stackTrace) {
|
||||
log('Error getting notification settings: $e', stackTrace: stackTrace);
|
||||
emit(NotificationSettingsStateError(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateNotificationSettings(Map<String, dynamic> data) async {
|
||||
try {
|
||||
final notificationSettings = await _notificationSettingsRepository.updateNotificationSettings(data);
|
||||
emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings));
|
||||
} catch (e, stackTrace) {
|
||||
log('Error updating notification settings: $e', stackTrace: stackTrace);
|
||||
emit(NotificationSettingsStateError(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
|
||||
|
||||
abstract class NotificationSettingsState extends Equatable {
|
||||
const NotificationSettingsState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class NotificationSettingsStateLoading extends NotificationSettingsState {
|
||||
const NotificationSettingsStateLoading();
|
||||
}
|
||||
|
||||
class NotificationSettingsStateLoaded extends NotificationSettingsState {
|
||||
final NotificationSettingsModel notificationSettings;
|
||||
|
||||
const NotificationSettingsStateLoaded({required this.notificationSettings});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [notificationSettings];
|
||||
}
|
||||
|
||||
class NotificationSettingsStateError extends NotificationSettingsState {
|
||||
final String errorMessage;
|
||||
|
||||
const NotificationSettingsStateError({required this.errorMessage});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [errorMessage];
|
||||
}
|
||||
@@ -67,6 +67,8 @@ class MenuScreen extends StatelessWidget {
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
|
||||
_divider(context),
|
||||
_tile(leading: 'assets/icons/notifications.svg', title: 'Уведомления', onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen')),
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
|
||||
_divider(context),
|
||||
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NotificationsSettingsScreen extends StatelessWidget {
|
||||
const NotificationsSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(create: (context) => sl<NotificationSettingsCubit>()..getNotificationSettings(), child: const NotificationsSettingsContent());
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationsSettingsContent extends StatelessWidget {
|
||||
const NotificationsSettingsContent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Уведомления')),
|
||||
body: BlocBuilder<NotificationSettingsCubit, NotificationSettingsState>(
|
||||
builder: (context, state) {
|
||||
if (state is NotificationSettingsStateLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is NotificationSettingsStateError) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Ошибка: ${state.errorMessage}'),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<NotificationSettingsCubit>().getNotificationSettings();
|
||||
},
|
||||
child: const Text('Повторить'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (state is NotificationSettingsStateLoaded) {
|
||||
return _buildSettingsContent(context, state.notificationSettings);
|
||||
}
|
||||
return const Center(child: Text('Нет данных'));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSettingsContent(BuildContext context, NotificationSettingsModel notificationSettings) {
|
||||
final cubit = context.read<NotificationSettingsCubit>();
|
||||
return Column(
|
||||
children: [
|
||||
_tile(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
onTap: () {},
|
||||
title: 'Уведомление о новых обновлениях',
|
||||
subtitle: null,
|
||||
trailing: CupertinoSwitch(
|
||||
value: notificationSettings.newUpdatesNotification,
|
||||
activeTrackColor: appTheme.blueA400,
|
||||
onChanged: (newValue) {
|
||||
_updateNotificationSetting(cubit, {'new_updates_notification': newValue});
|
||||
},
|
||||
),
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
onTap: () {},
|
||||
title: 'Уведомления о взвешивании',
|
||||
subtitle: null,
|
||||
trailing: CupertinoSwitch(
|
||||
value: notificationSettings.weightMeasurementsNotification,
|
||||
activeTrackColor: appTheme.blueA400,
|
||||
onChanged: (newValue) {
|
||||
_updateNotificationSetting(cubit, {'weight_measurements_notification': newValue});
|
||||
},
|
||||
),
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
onTap: () {},
|
||||
title: 'Email уведомления',
|
||||
subtitle: null,
|
||||
trailing: CupertinoSwitch(
|
||||
value: notificationSettings.isEmailNotificationsEnabled,
|
||||
activeTrackColor: appTheme.blueA400,
|
||||
onChanged: (newValue) {
|
||||
_updateNotificationSetting(cubit, {'is_email_notifications_enabled': newValue});
|
||||
},
|
||||
),
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
onTap: () {},
|
||||
title: 'Push уведомления',
|
||||
subtitle: null,
|
||||
trailing: CupertinoSwitch(
|
||||
value: notificationSettings.isPushNotificationsEnabled,
|
||||
activeTrackColor: appTheme.blueA400,
|
||||
onChanged: (newValue) {
|
||||
_updateNotificationSetting(cubit, {'is_push_notifications_enabled': newValue});
|
||||
},
|
||||
),
|
||||
),
|
||||
_divider(context),
|
||||
const SizedBox(height: 16),
|
||||
_buildDiaryRemindersSection(context, notificationSettings.diaryReminderNotifications, cubit),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDiaryRemindersSection(BuildContext context, DiaryReminderNotifications diaryReminderNotifications, NotificationSettingsCubit cubit) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'Напоминания о дневнике',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
_buildReminderTile(context, 'Первое напоминание', diaryReminderNotifications.firstReminder, 'first_reminder', cubit),
|
||||
_buildReminderTile(context, 'Второе напоминание', diaryReminderNotifications.secondReminder, 'second_reminder', cubit),
|
||||
_buildReminderTile(context, 'Третье напоминание', diaryReminderNotifications.thirdReminder, 'third_reminder', cubit),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _updateNotificationSetting(NotificationSettingsCubit cubit, Map<String, dynamic> data) {
|
||||
cubit.updateNotificationSettings(data);
|
||||
}
|
||||
|
||||
void _updateReminderSetting(NotificationSettingsCubit cubit, String reminderType, Map<String, dynamic> data) {
|
||||
final updateData = {
|
||||
'diary_reminder_notifications': {reminderType: data},
|
||||
};
|
||||
cubit.updateNotificationSettings(updateData);
|
||||
}
|
||||
|
||||
Widget _buildReminderTile(BuildContext context, String title, ReminderSettings reminder, String reminderType, NotificationSettingsCubit cubit) {
|
||||
return _tile(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
onTap: reminder.enabled
|
||||
? () {
|
||||
_showTimePicker(context, reminder, reminderType, cubit);
|
||||
}
|
||||
: null,
|
||||
title: title,
|
||||
subtitle: reminder.enabled
|
||||
? Row(
|
||||
children: [
|
||||
Icon(Icons.access_time, size: 16, color: appTheme.blueA400),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${reminder.localTimeHours.toString().padLeft(2, '0')}:${reminder.localTimeMins.toString().padLeft(2, '0')}',
|
||||
style: TextStyle(fontSize: 14, color: appTheme.blueA400, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text('(нажмите для изменения)', style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CupertinoSwitch(
|
||||
value: reminder.enabled,
|
||||
activeTrackColor: appTheme.blueA400,
|
||||
onChanged: (newValue) {
|
||||
_updateReminderSetting(cubit, reminderType, {
|
||||
'enabled': newValue,
|
||||
'local_time_hours': reminder.localTimeHours,
|
||||
'local_time_mins': reminder.localTimeMins,
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showTimePicker(BuildContext context, ReminderSettings reminder, String reminderType, NotificationSettingsCubit cubit) {
|
||||
int selectedHour = reminder.localTimeHours;
|
||||
int selectedMinute = reminder.localTimeMins;
|
||||
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
height: 300,
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
margin: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
color: CupertinoColors.systemBackground.resolveFrom(context),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
CupertinoButton(child: const Text('Отмена'), onPressed: () => Navigator.of(context).pop()),
|
||||
CupertinoButton(
|
||||
child: const Text('Готово'),
|
||||
onPressed: () {
|
||||
_updateReminderSetting(cubit, reminderType, {
|
||||
'enabled': reminder.enabled,
|
||||
'local_time_hours': selectedHour,
|
||||
'local_time_mins': selectedMinute,
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: CupertinoDatePicker(
|
||||
mode: CupertinoDatePickerMode.time,
|
||||
use24hFormat: true,
|
||||
initialDateTime: DateTime(2024, 1, 1, selectedHour, selectedMinute),
|
||||
onDateTimeChanged: (DateTime newDateTime) {
|
||||
selectedHour = newDateTime.hour;
|
||||
selectedMinute = newDateTime.minute;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ListTile _tile({required String title, required Function()? onTap, Widget? trailing, EdgeInsets? padding, Widget? subtitle}) {
|
||||
return ListTile(
|
||||
contentPadding: padding,
|
||||
onTap: onTap,
|
||||
title: Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1)),
|
||||
subtitle: subtitle,
|
||||
trailing: trailing ?? const Icon(Icons.arrow_forward_ios_rounded, size: 12, color: Colors.black38),
|
||||
);
|
||||
}
|
||||
|
||||
Center _divider(BuildContext context) {
|
||||
return Center(
|
||||
child: Container(
|
||||
height: 0.4,
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
|
||||
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
|
||||
|
||||
class NotificationSettingsRepository {
|
||||
final NotificationSettingsService _notificationSettingsService;
|
||||
|
||||
NotificationSettingsRepository(this._notificationSettingsService);
|
||||
|
||||
Future<NotificationSettingsModel> getNotificationSettings() async {
|
||||
return await _notificationSettingsService.getNotificationSettings();
|
||||
}
|
||||
|
||||
Future<NotificationSettingsModel> updateNotificationSettings(Map<String, dynamic> data) async {
|
||||
return await _notificationSettingsService.updateNotificationSettings(data: data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'notification_settings_service.g.dart';
|
||||
|
||||
@RestApi()
|
||||
abstract class NotificationSettingsService {
|
||||
factory NotificationSettingsService(Dio dio, {String baseUrl}) = _NotificationSettingsService;
|
||||
|
||||
@GET('/api/profile/notification_settings')
|
||||
Future<NotificationSettingsModel> getNotificationSettings();
|
||||
|
||||
@POST('/api/profile/update_notification_settings')
|
||||
Future<NotificationSettingsModel> updateNotificationSettings({@Body() required Map<String, dynamic> data});
|
||||
}
|
||||
@@ -58,8 +58,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
children: [
|
||||
_additionalInfo(state),
|
||||
const SizedBox(height: 12),
|
||||
_notifications(state),
|
||||
const SizedBox(height: 12),
|
||||
// _notifications(state),
|
||||
// const SizedBox(height: 12),
|
||||
_becomeConsultantButton(state),
|
||||
_deleteAccountButton(state, settingsCubit),
|
||||
],
|
||||
@@ -147,6 +147,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// ignore: unused_element
|
||||
Column _notifications(SettingsStateLoaded state) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
Reference in New Issue
Block a user