173 lines
5.4 KiB
Dart
173 lines
5.4 KiB
Dart
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:timezone/timezone.dart' as tz;
|
|
|
|
class ReminderService {
|
|
static final ReminderService _instance = ReminderService._internal();
|
|
factory ReminderService() => _instance;
|
|
ReminderService._internal();
|
|
|
|
final FlutterLocalNotificationsPlugin _notifications =
|
|
FlutterLocalNotificationsPlugin();
|
|
|
|
static const String _keyJamMasuk = 'reminder_jam_masuk';
|
|
static const String _keyJamPulang = 'reminder_jam_pulang';
|
|
|
|
static const int _baseIdMasuk = 1001;
|
|
static const int _baseIdPulang = 2001;
|
|
static const int _baseIdLupa = 3001;
|
|
static const int _daysAhead = 7;
|
|
static const int _minutesBefore = 15;
|
|
static const int _minutesAfterForLupa = 30;
|
|
|
|
Future<void> initialize() async {
|
|
const androidSettings =
|
|
AndroidInitializationSettings('@mipmap/ic_launcher');
|
|
const initSettings = InitializationSettings(android: androidSettings);
|
|
await _notifications.initialize(settings: initSettings);
|
|
}
|
|
|
|
Future<void> scheduleWeeklyReminders({
|
|
required String jadwalJamMasuk,
|
|
required String jadwalJamPulang,
|
|
required bool sudahAbsenMasuk,
|
|
required bool sudahAbsenPulang,
|
|
}) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_keyJamMasuk, jadwalJamMasuk);
|
|
await prefs.setString(_keyJamPulang, jadwalJamPulang);
|
|
|
|
await _cancelAllReminders();
|
|
|
|
final masukParts = _parseTime(jadwalJamMasuk);
|
|
final pulangParts = _parseTime(jadwalJamPulang);
|
|
|
|
if (masukParts == null || pulangParts == null) return;
|
|
|
|
final jakarta = tz.getLocation('Asia/Jakarta');
|
|
final now = tz.TZDateTime.now(jakarta);
|
|
|
|
for (int i = 0; i < _daysAhead; i++) {
|
|
final targetDate = now.add(Duration(days: i));
|
|
final isToday = i == 0;
|
|
|
|
|
|
final reminderMasuk = tz.TZDateTime(
|
|
jakarta,
|
|
targetDate.year,
|
|
targetDate.month,
|
|
targetDate.day,
|
|
masukParts[0],
|
|
masukParts[1],
|
|
).subtract(Duration(minutes: _minutesBefore));
|
|
|
|
if (!isToday || (!sudahAbsenMasuk && reminderMasuk.isAfter(now))) {
|
|
if (reminderMasuk.isAfter(now)) {
|
|
await _scheduleNotification(
|
|
id: _baseIdMasuk + i,
|
|
title: '⏰ Pengingat Absen Masuk',
|
|
body: 'Jangan lupa absen masuk! Jadwal masuk: $jadwalJamMasuk',
|
|
scheduledDate: reminderMasuk,
|
|
);
|
|
}
|
|
}
|
|
|
|
final reminderPulang = tz.TZDateTime(
|
|
jakarta,
|
|
targetDate.year,
|
|
targetDate.month,
|
|
targetDate.day,
|
|
pulangParts[0],
|
|
pulangParts[1],
|
|
).subtract(Duration(minutes: _minutesBefore));
|
|
|
|
if (!isToday || (!sudahAbsenPulang && reminderPulang.isAfter(now))) {
|
|
if (reminderPulang.isAfter(now)) {
|
|
await _scheduleNotification(
|
|
id: _baseIdPulang + i,
|
|
title: '⏰ Pengingat Absen Pulang',
|
|
body: 'Jangan lupa absen pulang! Jadwal pulang: $jadwalJamPulang',
|
|
scheduledDate: reminderPulang,
|
|
);
|
|
}
|
|
}
|
|
|
|
final reminderLupa = tz.TZDateTime(
|
|
jakarta,
|
|
targetDate.year,
|
|
targetDate.month,
|
|
targetDate.day,
|
|
masukParts[0],
|
|
masukParts[1],
|
|
).add(Duration(minutes: _minutesAfterForLupa));
|
|
|
|
if (!isToday || (!sudahAbsenMasuk && reminderLupa.isAfter(now))) {
|
|
if (reminderLupa.isAfter(now)) {
|
|
await _scheduleNotification(
|
|
id: _baseIdLupa + i,
|
|
title: '⚠️ Anda Belum Absen Masuk',
|
|
body:
|
|
'Sudah $_minutesAfterForLupa menit lewat jadwal masuk ($jadwalJamMasuk). Segera lakukan absen!',
|
|
scheduledDate: reminderLupa,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _scheduleNotification({
|
|
required int id,
|
|
required String title,
|
|
required String body,
|
|
required tz.TZDateTime scheduledDate,
|
|
}) async {
|
|
const androidDetails = AndroidNotificationDetails(
|
|
'attendance_reminder',
|
|
'Pengingat Absen',
|
|
channelDescription: 'Pengingat absen masuk dan pulang otomatis',
|
|
importance: Importance.max,
|
|
priority: Priority.max,
|
|
icon: '@mipmap/ic_launcher',
|
|
category: AndroidNotificationCategory.alarm,
|
|
fullScreenIntent: true,
|
|
styleInformation: BigTextStyleInformation(''),
|
|
);
|
|
const details = NotificationDetails(android: androidDetails);
|
|
|
|
await _notifications.zonedSchedule(
|
|
id: id,
|
|
title: title,
|
|
body: body,
|
|
scheduledDate: scheduledDate,
|
|
notificationDetails: details,
|
|
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
|
);
|
|
}
|
|
|
|
Future<void> _cancelAllReminders() async {
|
|
for (int i = 0; i < _daysAhead; i++) {
|
|
await _notifications.cancel(id: _baseIdMasuk + i);
|
|
await _notifications.cancel(id: _baseIdPulang + i);
|
|
await _notifications.cancel(id: _baseIdLupa + i);
|
|
}
|
|
}
|
|
|
|
Future<void> cancelAll() async {
|
|
await _cancelAllReminders();
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove(_keyJamMasuk);
|
|
await prefs.remove(_keyJamPulang);
|
|
}
|
|
|
|
List<int>? _parseTime(String? timeStr) {
|
|
if (timeStr == null || timeStr.isEmpty || timeStr == '-') return null;
|
|
try {
|
|
final parts = timeStr.split(':');
|
|
if (parts.length < 2) return null;
|
|
return [int.parse(parts[0]), int.parse(parts[1])];
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|