import 'package:flutter/material.dart'; import 'package:flutter/foundation.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_data; import '../models/event_model.dart'; class NotificationService { static NotificationService _instance = NotificationService._internal(); factory NotificationService() => _instance; // 생성자에서 플러그인을 주입받을 수 있도록 변경 NotificationService._internal([FlutterLocalNotificationsPlugin? plugin]) : _notificationsPlugin = plugin ?? FlutterLocalNotificationsPlugin(); // 테스트용 인스턴스 설정 메서드 static void setTestInstance(NotificationService instance) { _instance = instance; } // 테스트용 인스턴스 생성 포함한 새 메서드 @visibleForTesting static NotificationService createTestInstance(FlutterLocalNotificationsPlugin mockPlugin) { return NotificationService._internal(mockPlugin); } final FlutterLocalNotificationsPlugin _notificationsPlugin; bool _isInitialized = false; // 알림 서비스 초기화 Future initialize() async { if (_isInitialized) return; // 타임존 초기화 tz_data.initializeTimeZones(); // 안드로이드 설정 const AndroidInitializationSettings androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); // iOS 설정 const DarwinInitializationSettings iOSSettings = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, ); // 초기화 설정 const InitializationSettings initSettings = InitializationSettings( android: androidSettings, iOS: iOSSettings, ); // 알림 플러그인 초기화 await _notificationsPlugin.initialize( initSettings, onDidReceiveNotificationResponse: (NotificationResponse response) { // 알림 클릭 시 처리 debugPrint('알림 클릭: ${response.payload}'); }, ); _isInitialized = true; debugPrint('알림 서비스 초기화 완료'); } // 권한 요청 Future requestPermission() async { if (!_isInitialized) await initialize(); // iOS에서 권한 요청 final bool? result = await _notificationsPlugin .resolvePlatformSpecificImplementation() ?.requestPermissions( alert: true, badge: true, sound: true, ); return result ?? false; } // 즉시 알림 표시 Future showNotification({ required int id, required String title, required String body, String? payload, }) async { if (!_isInitialized) await initialize(); const AndroidNotificationDetails androidDetails = AndroidNotificationDetails( 'event_channel', '이벤트 알림', channelDescription: '이벤트 관련 알림을 표시합니다', importance: Importance.high, priority: Priority.high, showWhen: true, ); const DarwinNotificationDetails iOSDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ); const NotificationDetails notificationDetails = NotificationDetails( android: androidDetails, iOS: iOSDetails, ); await _notificationsPlugin.show( id, title, body, notificationDetails, payload: payload, ); } // 예약 알림 설정 Future scheduleNotification({ required int id, required String title, required String body, required DateTime scheduledDate, String? payload, }) async { if (!_isInitialized) await initialize(); final tz.TZDateTime scheduledTZDate = tz.TZDateTime.from( scheduledDate, tz.local, ); const AndroidNotificationDetails androidDetails = AndroidNotificationDetails( 'event_reminder_channel', '이벤트 리마인더', channelDescription: '예정된 이벤트에 대한 알림을 표시합니다', importance: Importance.high, priority: Priority.high, ); const DarwinNotificationDetails iOSDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ); const NotificationDetails notificationDetails = NotificationDetails( android: androidDetails, iOS: iOSDetails, ); await _notificationsPlugin.zonedSchedule( id, title, body, scheduledTZDate, notificationDetails, androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, matchDateTimeComponents: DateTimeComponents.time, payload: payload, ); } // 이벤트 시작 알림 설정 Future scheduleEventStartReminder(Event event) async { if (event.id.isEmpty) return; // 이벤트 시작 1시간 전 알림 final DateTime reminderTime = event.startDate.subtract(const Duration(hours: 1)); // 현재 시간이 알림 시간보다 이후라면 알림을 설정하지 않음 if (reminderTime.isBefore(DateTime.now())) return; await scheduleNotification( id: event.id.hashCode, title: '이벤트 시작 알림', body: '${event.title} 이벤트가 1시간 후에 시작됩니다.', scheduledDate: reminderTime, payload: event.id, ); } // 이벤트 등록 마감 알림 설정 Future scheduleRegistrationDeadlineReminder(Event event) async { if (event.id.isEmpty || event.registrationDeadline == null) return; // 등록 마감 1일 전 알림 final DateTime reminderTime = event.registrationDeadline!.subtract(const Duration(days: 1)); // 현재 시간이 알림 시간보다 이후라면 알림을 설정하지 않음 if (reminderTime.isBefore(DateTime.now())) return; await scheduleNotification( id: event.id.hashCode + 1, // 이벤트 시작 알림과 ID가 겹치지 않도록 함 title: '이벤트 등록 마감 알림', body: '${event.title} 이벤트의 등록이 내일 마감됩니다.', scheduledDate: reminderTime, payload: event.id, ); } // 특정 이벤트의 모든 알림 취소 Future cancelEventNotifications(String eventId) async { await _notificationsPlugin.cancel(eventId.hashCode); await _notificationsPlugin.cancel(eventId.hashCode + 1); } // 모든 알림 취소 Future cancelAllNotifications() async { await _notificationsPlugin.cancelAll(); } }