import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/status_provider.dart'; import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/models/settings.dart'; import 'package:refilc/helpers/notification_helper.i18n.dart'; import 'package:refilc_kreta_api/client/api.dart'; import 'package:refilc_kreta_api/client/client.dart'; import 'package:refilc_kreta_api/models/absence.dart'; import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/lesson.dart'; import 'package:refilc_kreta_api/models/week.dart'; import 'package:refilc_kreta_api/providers/grade_provider.dart'; import 'package:refilc_kreta_api/providers/timetable_provider.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message; import 'package:i18n_extension/i18n_extension.dart'; import 'package:intl/intl.dart'; import 'package:refilc_kreta_api/models/message.dart'; class NotificationsHelper { late DatabaseProvider database; late SettingsProvider settingsProvider; late UserProvider userProvider; late KretaClient kretaClient; FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); List combineLists( List list1, List list2, K Function(T) keyExtractor, ) { Set uniqueKeys = {}; List combinedList = []; for (T item in list1) { K key = keyExtractor(item); if (!uniqueKeys.contains(key)) { uniqueKeys.add(key); combinedList.add(item); } } for (T item in list2) { K key = keyExtractor(item); if (!uniqueKeys.contains(key)) { uniqueKeys.add(key); combinedList.add(item); } } return combinedList; } String dayTitle(DateTime date) { try { return DateFormat("EEEE", I18n.locale.languageCode).format(date); } catch (e) { return "Unknown"; } } @pragma('vm:entry-point') void backgroundJob() async { // initialize providers database = DatabaseProvider(); await database.init(); settingsProvider = await database.query.getSettings(database); userProvider = await database.query.getUsers(settingsProvider); if (userProvider.id != null && settingsProvider.notificationsEnabled) { // refresh kreta login final status = StatusProvider(); kretaClient = KretaClient( user: userProvider, settings: settingsProvider, status: status); kretaClient.refreshLogin(); if (settingsProvider.notificationsGradesEnabled) gradeNotification(); if (settingsProvider.notificationsAbsencesEnabled) absenceNotification(); if (settingsProvider.notificationsMessagesEnabled) messageNotification(); if (settingsProvider.notificationsLessonsEnabled) lessonNotification(); } } void gradeNotification() async { // fetch grades GradeProvider gradeProvider = GradeProvider( settings: settingsProvider, user: userProvider, database: database, kreta: kretaClient); gradeProvider.fetch(); List grades = await database.userQuery.getGrades(userId: userProvider.id ?? ""); DateTime lastSeenGrade = await database.userQuery.lastSeenGrade(userId: userProvider.id ?? ""); // loop through grades and see which hasn't been seen yet for (Grade grade in grades) { // if grade is not a normal grade (1-5), don't show it if ([1, 2, 3, 4, 5].contains(grade.value.value)) { // if the grade was added over a week ago, don't show it to avoid notification spam // it worked in reverse, cuz someone added a * -1 to it, but it has been fixed now :D // old code below // if (grade.seenDate.isAfter(lastSeenGrade) && // grade.date.difference(DateTime.now()).inDays * -1 < 7) { // new code from here :P if (grade.seenDate.isAfter(lastSeenGrade) && grade.date.difference(DateTime.now()).inDays < 7) { // send notificiation about new grade AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( 'GRADES', 'Jegyek', channelDescription: 'Értesítés jegyek beírásakor', importance: Importance.max, priority: Priority.max, color: settingsProvider.customAccentColor, ticker: 'Jegyek', ); NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); if (userProvider.getUsers().length == 1) { await flutterLocalNotificationsPlugin.show( grade.id.hashCode, "title_grade".i18n, "body_grade".i18n.fill( [ grade.value.value.toString(), grade.subject.isRenamed && settingsProvider.renamedSubjectsEnabled ? grade.subject.renamedTo! : grade.subject.name ], ), notificationDetails, ); } else { // multiple users are added, also display student name await flutterLocalNotificationsPlugin.show( grade.id.hashCode, "title_grade".i18n, "body_grade_multiuser".i18n.fill( [ userProvider.displayName!, grade.value.value.toString(), grade.subject.isRenamed && settingsProvider.renamedSubjectsEnabled ? grade.subject.renamedTo! : grade.subject.name ], ), notificationDetails, ); } } } } // set grade seen status gradeProvider.seenAll(); } void absenceNotification() async { // get absences from api List? absenceJson = await kretaClient .getAPI(KretaAPI.absences(userProvider.instituteCode ?? "")); List storedAbsences = await database.userQuery.getAbsences(userId: userProvider.id!); if (absenceJson == null) { return; } // format api absences to correct format while preserving isSeen value List absences = absenceJson.map((e) { Absence apiAbsence = Absence.fromJson(e); Absence storedAbsence = storedAbsences.firstWhere( (stored) => stored.id == apiAbsence.id, orElse: () => apiAbsence); apiAbsence.isSeen = storedAbsence.isSeen; return apiAbsence; }).toList(); List modifiedAbsences = []; if (absences != storedAbsences) { // remove absences that are not new absences.removeWhere((element) => storedAbsences.contains(element)); for (Absence absence in absences) { if (!absence.isSeen) { absence.isSeen = true; modifiedAbsences.add(absence); AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( 'ABSENCES', 'Hiányzások', channelDescription: 'Értesítés hiányzások beírásakor', importance: Importance.max, priority: Priority.max, color: settingsProvider.customAccentColor, ticker: 'Hiányzások', ); NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); if (userProvider.getUsers().length == 1) { await flutterLocalNotificationsPlugin.show( absence.id.hashCode, "title_absence" .i18n, // https://discord.com/channels/1111649116020285532/1153273625206591528 "body_absence".i18n.fill( [ DateFormat("yyyy-MM-dd").format(absence.date), absence.subject.isRenamed && settingsProvider.renamedSubjectsEnabled ? absence.subject.renamedTo! : absence.subject.name ], ), notificationDetails, ); } else { await flutterLocalNotificationsPlugin.show( absence.id.hashCode, "title_absence" .i18n, // https://discord.com/channels/1111649116020285532/1153273625206591528 "body_absence_multiuser".i18n.fill( [ userProvider.displayName!, DateFormat("yyyy-MM-dd").format(absence.date), absence.subject.isRenamed && settingsProvider.renamedSubjectsEnabled ? absence.subject.renamedTo! : absence.subject.name ], ), notificationDetails, ); } } } } // combine modified absences and storedabsences list and save them to the database List combinedAbsences = combineLists( modifiedAbsences, storedAbsences, (Absence absence) => absence.id, ); await database.userStore .storeAbsences(combinedAbsences, userId: userProvider.id!); } void messageNotification() async { // get messages from api List? messageJson = await kretaClient.getAPI(KretaAPI.messages("beerkezett")); List storedmessages = await database.userQuery.getMessages(userId: userProvider.id!); if (messageJson == null) { return; } // format api messages to correct format while preserving isSeen value // Parse messages List messages = []; await Future.wait(List.generate(messageJson.length, (index) { return () async { Map message = messageJson.cast()[index]; Map? innerMessageJson = await kretaClient .getAPI(KretaAPI.message(message["azonosito"].toString())); if (innerMessageJson != null) { messages.add( Message.fromJson(innerMessageJson, forceType: MessageType.inbox)); } }(); })); for (Message message in messages) { for (Message storedMessage in storedmessages) { if (message.id == storedMessage.id) { message.isSeen = storedMessage.isSeen; } } } List modifiedmessages = []; if (messages != storedmessages) { // remove messages that are not new messages.removeWhere((element) => storedmessages.contains(element)); for (Message message in messages) { if (!message.isSeen) { message.isSeen = true; modifiedmessages.add(message); AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( 'MESSAGES', 'Üzenetek', channelDescription: 'Értesítés kapott üzenetekkor', importance: Importance.max, priority: Priority.max, color: settingsProvider.customAccentColor, ticker: 'Üzenetek', ); NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); if (userProvider.getUsers().length == 1) { await flutterLocalNotificationsPlugin.show( message.id.hashCode, message.author, message.content.replaceAll(RegExp(r'<[^>]*>'), ''), notificationDetails, ); } else { await flutterLocalNotificationsPlugin.show( message.id.hashCode, "(${userProvider.displayName!}) ${message.author}", message.content.replaceAll(RegExp(r'<[^>]*>'), ''), notificationDetails, ); } } } } // combine modified messages and storedmessages list and save them to the database List combinedmessages = combineLists( modifiedmessages, storedmessages, (Message message) => message.id, ); await database.userStore .storeMessages(combinedmessages, userId: userProvider.id!); } void lessonNotification() async { // get lesson from api TimetableProvider timetableProvider = TimetableProvider( user: userProvider, database: database, kreta: kretaClient); List storedlessons = timetableProvider.lessons[Week.current()] ?? []; List? apilessons = timetableProvider.getWeek(Week.current()) ?? []; for (Lesson lesson in apilessons) { for (Lesson storedLesson in storedlessons) { if (lesson.id == storedLesson.id) { lesson.isSeen = storedLesson.isSeen; } } } List modifiedlessons = []; if (apilessons != storedlessons) { // remove lessons that are not new apilessons.removeWhere((element) => storedlessons.contains(element)); for (Lesson lesson in apilessons) { if (!lesson.isSeen && lesson.isChanged) { lesson.isSeen = true; modifiedlessons.add(lesson); AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( 'LESSONS', 'Órák', channelDescription: 'Értesítés órák elmaradásáról, helyettesítésről', importance: Importance.max, priority: Priority.max, color: settingsProvider.customAccentColor, ticker: 'Órák', ); NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); if (userProvider.getUsers().length == 1) { if (lesson.status?.name == "Elmaradt") { switch (I18n.localeStr) { case "en_en": { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_canceled".i18n.fill( [ lesson.lessonIndex, lesson.name, dayTitle(lesson.date) ], ), notificationDetails, ); break; } case "hu_hu": { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_canceled".i18n.fill( [ dayTitle(lesson.date), lesson.lessonIndex, lesson.name ], ), notificationDetails, ); break; } default: { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_canceled".i18n.fill( [ lesson.lessonIndex, lesson.name, dayTitle(lesson.date) ], ), notificationDetails, ); break; } } } else if (lesson.substituteTeacher?.name != "") { switch (I18n.localeStr) { case "en_en": { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_substituted".i18n.fill( [ lesson.lessonIndex, lesson.name, dayTitle(lesson.date), lesson.substituteTeacher!.isRenamed ? lesson.substituteTeacher!.renamedTo! : lesson.substituteTeacher!.name ], ), notificationDetails, ); break; } case "hu_hu": { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_substituted".i18n.fill( [ dayTitle(lesson.date), lesson.lessonIndex, lesson.name, lesson.substituteTeacher!.isRenamed ? lesson.substituteTeacher!.renamedTo! : lesson.substituteTeacher!.name ], ), notificationDetails, ); break; } default: { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_substituted".i18n.fill( [ lesson.lessonIndex, lesson.name, dayTitle(lesson.date), lesson.substituteTeacher!.isRenamed ? lesson.substituteTeacher!.renamedTo! : lesson.substituteTeacher!.name ], ), notificationDetails, ); break; } } } } else { if (lesson.status?.name == "Elmaradt") { switch (I18n.localeStr) { case "en_en": { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_canceled".i18n.fill( [ userProvider.displayName!, lesson.lessonIndex, lesson.name, dayTitle(lesson.date) ], ), notificationDetails, ); break; } case "hu_hu": { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_canceled".i18n.fill( [ userProvider.displayName!, dayTitle(lesson.date), lesson.lessonIndex, lesson.name ], ), notificationDetails, ); break; } default: { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_canceled".i18n.fill( [ userProvider.displayName!, lesson.lessonIndex, lesson.name, dayTitle(lesson.date) ], ), notificationDetails, ); break; } } } else if (lesson.substituteTeacher?.name != "") { switch (I18n.localeStr) { case "en_en": { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_substituted".i18n.fill( [ userProvider.displayName!, lesson.lessonIndex, lesson.name, dayTitle(lesson.date), lesson.substituteTeacher!.isRenamed ? lesson.substituteTeacher!.renamedTo! : lesson.substituteTeacher!.name ], ), notificationDetails, ); break; } case "hu_hu": { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_substituted".i18n.fill( [ userProvider.displayName!, dayTitle(lesson.date), lesson.lessonIndex, lesson.name, lesson.substituteTeacher!.isRenamed ? lesson.substituteTeacher!.renamedTo! : lesson.substituteTeacher!.name ], ), notificationDetails, ); break; } default: { await flutterLocalNotificationsPlugin.show( lesson.id.hashCode, "title_lesson".i18n, "body_lesson_substituted".i18n.fill( [ userProvider.displayName!, lesson.lessonIndex, lesson.name, dayTitle(lesson.date), lesson.substituteTeacher!.isRenamed ? lesson.substituteTeacher!.renamedTo! : lesson.substituteTeacher!.name ], ), notificationDetails, ); break; } } } } } } // combine modified lesson and storedlesson list and save them to the database List combinedlessons = combineLists( modifiedlessons, storedlessons, (Lesson message) => message.id, ); Map> timetableLessons = timetableProvider.lessons; timetableLessons[Week.current()] = combinedlessons; await database.userStore .storeLessons(timetableLessons, userId: userProvider.id!); } } }