Add Local Notifications with deadline reminders
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
class NotificationService {
|
||||
static final NotificationService _instance = NotificationService._internal();
|
||||
factory NotificationService() => _instance;
|
||||
NotificationService._internal();
|
||||
|
||||
final FlutterLocalNotificationsPlugin _notifications = FlutterLocalNotificationsPlugin();
|
||||
bool _isInitialized = false;
|
||||
|
||||
// Инициализация
|
||||
Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
// Инициализируем timezone
|
||||
tz.initializeTimeZones();
|
||||
|
||||
// Настройки для Android
|
||||
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
|
||||
// Настройки для iOS
|
||||
const iosSettings = DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
);
|
||||
|
||||
const settings = InitializationSettings(
|
||||
android: androidSettings,
|
||||
iOS: iosSettings,
|
||||
);
|
||||
|
||||
await _notifications.initialize(
|
||||
settings,
|
||||
onDidReceiveNotificationResponse: _onNotificationTapped,
|
||||
onDidReceiveBackgroundNotificationResponse: _onBackgroundNotificationTapped,
|
||||
);
|
||||
|
||||
// Запрашиваем разрешения
|
||||
await _requestPermissions();
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
// Запрос разрешений
|
||||
Future<void> _requestPermissions() async {
|
||||
// Android 13+ требует запрос разрешений
|
||||
final androidPlugin = _notifications
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
|
||||
|
||||
if (androidPlugin != null) {
|
||||
await androidPlugin.requestNotificationsPermission();
|
||||
}
|
||||
|
||||
// iOS
|
||||
final iosPlugin = _notifications
|
||||
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>();
|
||||
|
||||
if (iosPlugin != null) {
|
||||
await iosPlugin.requestPermissions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка нажатия на уведомление
|
||||
void _onNotificationTapped(NotificationResponse response) {
|
||||
print('Notification tapped: ${response.payload}');
|
||||
// TODO: Навигация к задаче
|
||||
}
|
||||
|
||||
// Обработка нажатия в фоне
|
||||
@pragma('vm:entry-point')
|
||||
static void _onBackgroundNotificationTapped(NotificationResponse response) {
|
||||
print('Background notification tapped: ${response.payload}');
|
||||
}
|
||||
|
||||
// Создание канала для уведомлений о задачах
|
||||
Future<void> _createTaskChannel() async {
|
||||
const channel = AndroidNotificationChannel(
|
||||
'tasks_channel',
|
||||
'Задачи',
|
||||
description: 'Уведомления о задачах и дедлайнах',
|
||||
importance: Importance.high,
|
||||
enableVibration: true,
|
||||
playSound: true,
|
||||
);
|
||||
|
||||
await _notifications
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
// Создание канала для будильников
|
||||
Future<void> _createAlarmChannel() async {
|
||||
const channel = AndroidNotificationChannel(
|
||||
'alarms_channel',
|
||||
'Будильники',
|
||||
description: 'Будильники и напоминания',
|
||||
importance: Importance.max,
|
||||
enableVibration: true,
|
||||
playSound: true,
|
||||
);
|
||||
|
||||
await _notifications
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
// Показ немедленного уведомления
|
||||
Future<void> showNotification({
|
||||
required int id,
|
||||
required String title,
|
||||
required String body,
|
||||
String? payload,
|
||||
String channelId = 'tasks_channel',
|
||||
}) async {
|
||||
if (!_isInitialized) await initialize();
|
||||
|
||||
const details = NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'tasks_channel',
|
||||
'Задачи',
|
||||
channelDescription: 'Уведомления о задачах',
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
icon: '@mipmap/ic_launcher',
|
||||
enableVibration: true,
|
||||
playSound: true,
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
),
|
||||
);
|
||||
|
||||
await _notifications.show(
|
||||
id,
|
||||
title,
|
||||
body,
|
||||
details,
|
||||
payload: payload,
|
||||
);
|
||||
}
|
||||
|
||||
// Планирование уведомления
|
||||
Future<void> scheduleNotification({
|
||||
required int id,
|
||||
required String title,
|
||||
required String body,
|
||||
required DateTime scheduledDate,
|
||||
String? payload,
|
||||
String channelId = 'tasks_channel',
|
||||
}) async {
|
||||
if (!_isInitialized) await initialize();
|
||||
|
||||
// Не планируем в прошлом
|
||||
if (scheduledDate.isBefore(DateTime.now())) {
|
||||
scheduledDate = DateTime.now().add(const Duration(minutes: 1));
|
||||
}
|
||||
|
||||
const details = NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'tasks_channel',
|
||||
'Задачи',
|
||||
channelDescription: 'Уведомления о задачах',
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
icon: '@mipmap/ic_launcher',
|
||||
enableVibration: true,
|
||||
playSound: true,
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
),
|
||||
);
|
||||
|
||||
await _notifications.zonedSchedule(
|
||||
id,
|
||||
title,
|
||||
body,
|
||||
tz.TZDateTime.from(scheduledDate, tz.local),
|
||||
details,
|
||||
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
payload: payload,
|
||||
);
|
||||
}
|
||||
|
||||
// Отмена уведомления
|
||||
Future<void> cancelNotification(int id) async {
|
||||
await _notifications.cancel(id);
|
||||
}
|
||||
|
||||
// Отмена всех уведомлений
|
||||
Future<void> cancelAllNotifications() async {
|
||||
await _notifications.cancelAll();
|
||||
}
|
||||
|
||||
// Уведомление о дедлайне задачи
|
||||
Future<void> showTaskDeadlineReminder({
|
||||
required String taskTitle,
|
||||
required DateTime deadline,
|
||||
String? taskId,
|
||||
}) async {
|
||||
final hoursUntilDeadline = deadline.difference(DateTime.now()).inHours;
|
||||
|
||||
String title;
|
||||
String body;
|
||||
|
||||
if (hoursUntilDeadline <= 0) {
|
||||
title = '🔴 Дедлайн задачи!';
|
||||
body = 'Задача "$taskTitle" должна быть выполнена прямо сейчас!';
|
||||
} else if (hoursUntilDeadline <= 1) {
|
||||
title = '🟠 Срочно! Дедлайн через час';
|
||||
body = 'Задача "$taskTitle" должна быть выполнена через $hoursUntilDeadline ч';
|
||||
} else if (hoursUntilDeadline <= 24) {
|
||||
title = '🟡 Напоминание о дедлайне';
|
||||
body = 'Задача "$taskTitle" должна быть выполнена через $hoursUntilDeadline ч';
|
||||
} else {
|
||||
final days = hoursUntilDeadline ~/ 24;
|
||||
title = '🔵 Скоро дедлайн';
|
||||
body = 'Задача "$taskTitle" должна быть выполнена через $days дн';
|
||||
}
|
||||
|
||||
await showNotification(
|
||||
id: taskId.hashCode,
|
||||
title: title,
|
||||
body: body,
|
||||
payload: taskId,
|
||||
);
|
||||
}
|
||||
|
||||
// Уведомление о новой задаче
|
||||
Future<void> showNewTaskNotification({
|
||||
required String taskTitle,
|
||||
String? taskId,
|
||||
}) async {
|
||||
await showNotification(
|
||||
id: taskId.hashCode,
|
||||
title: '📋 Новая задача',
|
||||
body: 'Задача "$taskTitle" создана',
|
||||
payload: taskId,
|
||||
);
|
||||
}
|
||||
|
||||
// Уведомление о назначении задачи
|
||||
Future<void> showTaskAssignedNotification({
|
||||
required String taskTitle,
|
||||
required String assignedBy,
|
||||
String? taskId,
|
||||
}) async {
|
||||
await showNotification(
|
||||
id: taskId.hashCode,
|
||||
title: '🎯 Вам назначена задача',
|
||||
body: '$assignedBy назначил вас на задачу "$taskTitle"',
|
||||
payload: taskId,
|
||||
);
|
||||
}
|
||||
|
||||
// Будильник
|
||||
Future<void> showAlarmNotification({
|
||||
required int id,
|
||||
required String title,
|
||||
required String body,
|
||||
}) async {
|
||||
if (!_isInitialized) await initialize();
|
||||
|
||||
const details = NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'alarms_channel',
|
||||
'Будильники',
|
||||
channelDescription: 'Будильники и напоминания',
|
||||
importance: Importance.max,
|
||||
priority: Priority.max,
|
||||
icon: '@mipmap/ic_launcher',
|
||||
enableVibration: true,
|
||||
playSound: true,
|
||||
fullScreenIntent: true,
|
||||
category: AndroidNotificationCategory.alarm,
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
interruptionLevel: InterruptionLevel.timeSensitive,
|
||||
),
|
||||
);
|
||||
|
||||
await _notifications.show(
|
||||
id,
|
||||
title,
|
||||
body,
|
||||
details,
|
||||
);
|
||||
}
|
||||
|
||||
// Повторяющееся уведомление (ежедневное)
|
||||
Future<void> scheduleDailyNotification({
|
||||
required int id,
|
||||
required String title,
|
||||
required String body,
|
||||
required Time scheduledTime,
|
||||
String? payload,
|
||||
}) async {
|
||||
if (!_isInitialized) await initialize();
|
||||
|
||||
const details = NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'daily_reminders',
|
||||
'Ежедневные напоминания',
|
||||
channelDescription: 'Ежедневные уведомления',
|
||||
importance: Importance.defaultImportance,
|
||||
priority: Priority.defaultPriority,
|
||||
icon: '@mipmap/ic_launcher',
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
),
|
||||
);
|
||||
|
||||
await _notifications.zonedSchedule(
|
||||
id,
|
||||
title,
|
||||
body,
|
||||
_nextInstanceOfTime(scheduledTime),
|
||||
details,
|
||||
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
matchDateTimeComponents: DateTimeComponents.time,
|
||||
payload: payload,
|
||||
);
|
||||
}
|
||||
|
||||
tz.TZDateTime _nextInstanceOfTime(Time scheduledTime) {
|
||||
final now = tz.TZDateTime.now(tz.local);
|
||||
var scheduled = tz.TZDateTime(
|
||||
tz.local,
|
||||
now.year,
|
||||
now.month,
|
||||
now.day,
|
||||
scheduledTime.hour,
|
||||
scheduledTime.minute,
|
||||
);
|
||||
|
||||
if (scheduled.isBefore(now)) {
|
||||
scheduled = scheduled.add(const Duration(days: 1));
|
||||
}
|
||||
|
||||
return scheduled;
|
||||
}
|
||||
}
|
||||
|
||||
final notificationService = NotificationService();
|
||||
@@ -11,6 +11,7 @@ import '../screens/ai_test_screen.dart';
|
||||
import '../screens/admin/admin_screen.dart';
|
||||
import '../screens/admin/team_management_screen.dart';
|
||||
import '../screens/admin/projects_management_screen.dart';
|
||||
import '../screens/admin/notifications_settings_screen.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
|
||||
// Глобальный ключ для навигации
|
||||
@@ -96,6 +97,12 @@ class AppRouter {
|
||||
name: 'admin-projects',
|
||||
builder: (context, state) => const ProjectsManagementScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'notifications',
|
||||
name: 'admin-notifications',
|
||||
builder: (context, state) =>
|
||||
const NotificationsSettingsScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user