Files
Avtex/lib/core/notification_service.dart
T

366 lines
11 KiB
Dart

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();