Line data Source code
1 : import 'package:flutter/material.dart';
2 : import 'package:flutter/foundation.dart';
3 : import 'package:flutter_local_notifications/flutter_local_notifications.dart';
4 : import 'package:timezone/timezone.dart' as tz;
5 : import 'package:timezone/data/latest.dart' as tz_data;
6 :
7 : import '../models/event_model.dart';
8 :
9 : class NotificationService {
10 3 : static NotificationService _instance = NotificationService._internal();
11 2 : factory NotificationService() => _instance;
12 :
13 : // 생성자에서 플러그인을 주입받을 수 있도록 변경
14 1 : NotificationService._internal([FlutterLocalNotificationsPlugin? plugin])
15 1 : : _notificationsPlugin = plugin ?? FlutterLocalNotificationsPlugin();
16 :
17 : // 테스트용 인스턴스 설정 메서드
18 1 : static void setTestInstance(NotificationService instance) {
19 : _instance = instance;
20 : }
21 :
22 : // 테스트용 인스턴스 생성 포함한 새 메서드
23 1 : @visibleForTesting
24 : static NotificationService createTestInstance(FlutterLocalNotificationsPlugin mockPlugin) {
25 1 : return NotificationService._internal(mockPlugin);
26 : }
27 :
28 : final FlutterLocalNotificationsPlugin _notificationsPlugin;
29 : bool _isInitialized = false;
30 :
31 : // 알림 서비스 초기화
32 1 : Future<void> initialize() async {
33 1 : if (_isInitialized) return;
34 :
35 : // 타임존 초기화
36 1 : tz_data.initializeTimeZones();
37 :
38 : // 안드로이드 설정
39 : const AndroidInitializationSettings androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
40 :
41 : // iOS 설정
42 : const DarwinInitializationSettings iOSSettings = DarwinInitializationSettings(
43 : requestAlertPermission: true,
44 : requestBadgePermission: true,
45 : requestSoundPermission: true,
46 : );
47 :
48 : // 초기화 설정
49 : const InitializationSettings initSettings = InitializationSettings(
50 : android: androidSettings,
51 : iOS: iOSSettings,
52 : );
53 :
54 : // 알림 플러그인 초기화
55 2 : await _notificationsPlugin.initialize(
56 : initSettings,
57 0 : onDidReceiveNotificationResponse: (NotificationResponse response) {
58 : // 알림 클릭 시 처리
59 0 : debugPrint('알림 클릭: ${response.payload}');
60 : },
61 : );
62 :
63 1 : _isInitialized = true;
64 2 : debugPrint('알림 서비스 초기화 완료');
65 : }
66 :
67 : // 권한 요청
68 1 : Future<bool> requestPermission() async {
69 2 : if (!_isInitialized) await initialize();
70 :
71 : // iOS에서 권한 요청
72 1 : final bool? result = await _notificationsPlugin
73 1 : .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
74 1 : ?.requestPermissions(
75 : alert: true,
76 : badge: true,
77 : sound: true,
78 : );
79 :
80 : return result ?? false;
81 : }
82 :
83 : // 즉시 알림 표시
84 0 : Future<void> showNotification({
85 : required int id,
86 : required String title,
87 : required String body,
88 : String? payload,
89 : }) async {
90 0 : if (!_isInitialized) await initialize();
91 :
92 : const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
93 : 'event_channel',
94 : '이벤트 알림',
95 : channelDescription: '이벤트 관련 알림을 표시합니다',
96 : importance: Importance.high,
97 : priority: Priority.high,
98 : showWhen: true,
99 : );
100 :
101 : const DarwinNotificationDetails iOSDetails = DarwinNotificationDetails(
102 : presentAlert: true,
103 : presentBadge: true,
104 : presentSound: true,
105 : );
106 :
107 : const NotificationDetails notificationDetails = NotificationDetails(
108 : android: androidDetails,
109 : iOS: iOSDetails,
110 : );
111 :
112 0 : await _notificationsPlugin.show(
113 : id,
114 : title,
115 : body,
116 : notificationDetails,
117 : payload: payload,
118 : );
119 : }
120 :
121 : // 예약 알림 설정
122 1 : Future<void> scheduleNotification({
123 : required int id,
124 : required String title,
125 : required String body,
126 : required DateTime scheduledDate,
127 : String? payload,
128 : }) async {
129 2 : if (!_isInitialized) await initialize();
130 :
131 1 : final tz.TZDateTime scheduledTZDate = tz.TZDateTime.from(
132 : scheduledDate,
133 1 : tz.local,
134 : );
135 :
136 : const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
137 : 'event_reminder_channel',
138 : '이벤트 리마인더',
139 : channelDescription: '예정된 이벤트에 대한 알림을 표시합니다',
140 : importance: Importance.high,
141 : priority: Priority.high,
142 : );
143 :
144 : const DarwinNotificationDetails iOSDetails = DarwinNotificationDetails(
145 : presentAlert: true,
146 : presentBadge: true,
147 : presentSound: true,
148 : );
149 :
150 : const NotificationDetails notificationDetails = NotificationDetails(
151 : android: androidDetails,
152 : iOS: iOSDetails,
153 : );
154 :
155 2 : await _notificationsPlugin.zonedSchedule(
156 : id,
157 : title,
158 : body,
159 : scheduledTZDate,
160 : notificationDetails,
161 : androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
162 : matchDateTimeComponents: DateTimeComponents.time,
163 : payload: payload,
164 : );
165 : }
166 :
167 : // 이벤트 시작 알림 설정
168 1 : Future<void> scheduleEventStartReminder(Event event) async {
169 2 : if (event.id.isEmpty) return;
170 :
171 : // 이벤트 시작 1시간 전 알림
172 2 : final DateTime reminderTime = event.startDate.subtract(const Duration(hours: 1));
173 :
174 : // 현재 시간이 알림 시간보다 이후라면 알림을 설정하지 않음
175 2 : if (reminderTime.isBefore(DateTime.now())) return;
176 :
177 1 : await scheduleNotification(
178 2 : id: event.id.hashCode,
179 : title: '이벤트 시작 알림',
180 2 : body: '${event.title} 이벤트가 1시간 후에 시작됩니다.',
181 : scheduledDate: reminderTime,
182 1 : payload: event.id,
183 : );
184 : }
185 :
186 : // 이벤트 등록 마감 알림 설정
187 0 : Future<void> scheduleRegistrationDeadlineReminder(Event event) async {
188 0 : if (event.id.isEmpty || event.registrationDeadline == null) return;
189 :
190 : // 등록 마감 1일 전 알림
191 0 : final DateTime reminderTime = event.registrationDeadline!.subtract(const Duration(days: 1));
192 :
193 : // 현재 시간이 알림 시간보다 이후라면 알림을 설정하지 않음
194 0 : if (reminderTime.isBefore(DateTime.now())) return;
195 :
196 0 : await scheduleNotification(
197 0 : id: event.id.hashCode + 1, // 이벤트 시작 알림과 ID가 겹치지 않도록 함
198 : title: '이벤트 등록 마감 알림',
199 0 : body: '${event.title} 이벤트의 등록이 내일 마감됩니다.',
200 : scheduledDate: reminderTime,
201 0 : payload: event.id,
202 : );
203 : }
204 :
205 : // 특정 이벤트의 모든 알림 취소
206 0 : Future<void> cancelEventNotifications(String eventId) async {
207 0 : await _notificationsPlugin.cancel(eventId.hashCode);
208 0 : await _notificationsPlugin.cancel(eventId.hashCode + 1);
209 : }
210 :
211 : // 모든 알림 취소
212 0 : Future<void> cancelAllNotifications() async {
213 0 : await _notificationsPlugin.cancelAll();
214 : }
215 : }
|