forked from eatvio/eatvio_app
feat(notifications): Issue-236
This commit is contained in:
@@ -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)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user