[APIv2] Notifications - DB entity, move to APIv2. Add Server event sync and web push.

This commit is contained in:
Kuba Szczodrzyński 2019-10-18 22:12:40 +02:00
parent 24ab2e7795
commit 8dc358b075
23 changed files with 865 additions and 99 deletions

View File

@ -8,20 +8,21 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.receivers.BootReceiver;
import pl.szczodrzynski.edziennik.sync.SyncJob;
import pl.szczodrzynski.edziennik.sync.SyncService;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT;
import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
@ -36,14 +37,14 @@ public class Notifier {
private static String CHANNEL_GET_DATA_DESC;
private static final String GROUP_KEY_GET_DATA = "pl.szczodrzynski.edziennik.GET_DATA";
private static final int ID_NOTIFICATIONS = 1337002;
private static String CHANNEL_NOTIFICATIONS_NAME;
private static String CHANNEL_NOTIFICATIONS_DESC;
public static final int ID_NOTIFICATIONS = 1337002;
public static String CHANNEL_NOTIFICATIONS_NAME;
public static String CHANNEL_NOTIFICATIONS_DESC;
public static final String GROUP_KEY_NOTIFICATIONS = "pl.szczodrzynski.edziennik.NOTIFICATIONS";
private static final int ID_NOTIFICATIONS_QUIET = 1337002;
private static String CHANNEL_NOTIFICATIONS_QUIET_NAME;
private static String CHANNEL_NOTIFICATIONS_QUIET_DESC;
public static final int ID_NOTIFICATIONS_QUIET = 1337002;
public static String CHANNEL_NOTIFICATIONS_QUIET_NAME;
public static String CHANNEL_NOTIFICATIONS_QUIET_DESC;
public static final String GROUP_KEY_NOTIFICATIONS_QUIET = "pl.szczodrzynski.edziennik.NOTIFICATIONS_QUIET";
private static final int ID_UPDATES = 1337003;
@ -52,9 +53,9 @@ public class Notifier {
private static final String GROUP_KEY_UPDATES = "pl.szczodrzynski.edziennik.UPDATES";
private App app;
private NotificationManager notificationManager;
public NotificationManager notificationManager;
private NotificationCompat.Builder getDataNotificationBuilder;
private int notificationColor;
public int notificationColor;
Notifier(App _app) {
this.app = _app;
@ -109,13 +110,13 @@ public class Notifier {
return app.appConfig.quietHoursStart > 0 && now >= start && now <= end;
}
private int getNotificationDefaults() {
public int getNotificationDefaults() {
return (shouldBeQuiet() ? 0 : Notification.DEFAULT_ALL);
}
private String getNotificationGroup() {
public String getNotificationGroup() {
return shouldBeQuiet() ? GROUP_KEY_NOTIFICATIONS_QUIET : GROUP_KEY_NOTIFICATIONS;
}
private int getNotificationPriority() {
public int getNotificationPriority() {
return shouldBeQuiet() ? PRIORITY_DEFAULT : PRIORITY_MAX;
}

View File

@ -13,8 +13,13 @@ import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.events.*
import pl.szczodrzynski.edziennik.api.v2.events.SyncErrorEvent
import pl.szczodrzynski.edziennik.api.v2.events.SyncFinishedEvent
import pl.szczodrzynski.edziennik.api.v2.events.SyncProfileFinishedEvent
import pl.szczodrzynski.edziennik.api.v2.events.SyncProgressEvent
import pl.szczodrzynski.edziennik.api.v2.events.requests.*
import pl.szczodrzynski.edziennik.api.v2.events.task.ErrorReportTask
import pl.szczodrzynski.edziennik.api.v2.events.task.NotifyTask
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.librus.Librus
@ -25,7 +30,7 @@ import pl.szczodrzynski.edziennik.api.v2.template.Template
import pl.szczodrzynski.edziennik.api.v2.vulcan.Vulcan
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
import kotlin.math.max
import kotlin.math.min
class ApiService : Service() {
@ -39,6 +44,7 @@ class ApiService : Service() {
private val taskQueue = mutableListOf<ApiTask>()
private val errorList = mutableListOf<ApiError>()
private var queueHasErrorReportTask = false
private var queueHasNotifyTask = false
private var serviceClosed = false
private var taskCancelled = false
@ -64,9 +70,14 @@ class ApiService : Service() {
private val taskCallback = object : EdziennikCallback {
override fun onCompleted() {
edziennikInterface = null
if (taskRunningObject is SyncProfileRequest) {
// post an event if this task is a sync, not e.g. first login or message getting
if (!taskCancelled) {
EventBus.getDefault().post(SyncProfileFinishedEvent(taskProfileId))
}
// add a notifying task to create data notifications of this profile
addNotifyTask()
}
notification.setIdle().post()
taskRunningObject = null
taskRunning = false
@ -86,6 +97,12 @@ class ApiService : Service() {
errorList.add(apiError)
apiError.throwable?.printStackTrace()
if (apiError.isCritical) {
// if this error ends the sync, post an error notification
// if this is a sync task, create a notifying task
if (taskRunningObject is SyncProfileRequest) {
// add a notifying task to create data notifications of this profile
addNotifyTask()
}
notification.setCriticalError().post()
taskRunningObject = null
taskRunning = false
@ -109,6 +126,18 @@ class ApiService : Service() {
EventBus.getDefault().post(SyncProgressEvent(taskProfileId, taskProfileName, taskProgress, taskProgressRes))
notification.setProgressRes(taskProgressRes!!).post()
}
fun addNotifyTask() {
if (!queueHasNotifyTask) {
queueHasNotifyTask = true
taskQueue.add(
if (queueHasErrorReportTask) max(taskQueue.size-1, 0) else taskQueue.size,
NotifyTask().apply {
taskId = ++taskMaximumId
}
)
}
}
}
/* _______ _ _ _
@ -134,15 +163,17 @@ class ApiService : Service() {
if (task is ErrorReportTask) {
queueHasErrorReportTask = false
notification
.setCurrentTask(taskRunningId, null)
.setProgressRes(R.string.edziennik_notification_api_error_report_title)
.post()
errorList.forEach { error ->
d(TAG, "Error ${error.tag} profile ${error.profileId}: code ${error.errorCode}")
task.run(notification, errorList)
taskRunningObject = null
taskRunning = false
taskRunningId = -1
sync()
return
}
errorList.clear()
notification.setIdle().post()
if (task is NotifyTask) {
queueHasNotifyTask = false
task.run(app)
taskRunningObject = null
taskRunning = false
taskRunningId = -1

View File

@ -0,0 +1,210 @@
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.*
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_ANNOUNCEMENT
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_ATTENDANCE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_EVENT
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_GRADE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_MESSAGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_NOTICE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_TIMETABLE_LESSON_CHANGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Date
class DataNotifications(val data: Data) {
companion object {
private const val TAG = "DataNotifications"
}
val app = data.app
val profileId = data.profile?.id ?: -1
val profileName = data.profile?.name ?: ""
val profile = data.profile
val loginStore = data.loginStore
init { run {
if (profile == null) {
return@run
}
for (change in app.db.lessonChangeDao().getNotNotifiedNow(profileId)) {
val text = app.getString(R.string.notification_lesson_change_format, change.changeTypeStr(app), if (change.lessonDate == null) "" else change.lessonDate!!.formattedString, change.subjectLongName)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_TIMETABLE_LESSON_CHANGE),
text = text,
type = TYPE_TIMETABLE_LESSON_CHANGE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_TIMETABLE,
addedDate = change.addedDate
).addExtra("timetableDate", change.lessonDate?.value?.toLong())
}
for (event in app.db.eventDao().getNotNotifiedNow(profileId)) {
val text = if (event.type == Event.TYPE_HOMEWORK)
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_homework_no_subject_format
else
R.string.notification_homework_format,
event.subjectLongName,
event.eventDate.formattedString
)
else
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_event_no_subject_format
else
R.string.notification_event_format,
event.typeName,
event.eventDate.formattedString,
event.subjectLongName
)
val type = if (event.type == Event.TYPE_HOMEWORK) TYPE_NEW_HOMEWORK else TYPE_NEW_EVENT
data.notifications += Notification(
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = profileId,
profileName = profileName,
viewId = if (event.type == Event.TYPE_HOMEWORK) DRAWER_ITEM_HOMEWORK else DRAWER_ITEM_AGENDA,
addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
}
val today = Date.getToday()
val todayValue = today.value
profile.currentSemester = profile.dateToSemester(today)
for (grade in app.db.gradeDao().getNotNotifiedNow(profileId)) {
val gradeName = when (grade.type) {
TYPE_SEMESTER1_PROPOSED, TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format_2, grade.name)
TYPE_SEMESTER1_FINAL, TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format_2, grade.name)
TYPE_YEAR_PROPOSED -> app.getString(R.string.grade_year_proposed_format_2, grade.name)
TYPE_YEAR_FINAL -> app.getString(R.string.grade_year_final_format_2, grade.name)
else -> grade.name
}
val text = app.getString(R.string.notification_grade_format, gradeName, grade.subjectLongName)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_GRADE),
text = text,
type = TYPE_NEW_GRADE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_GRADES,
addedDate = grade.addedDate
).addExtra("gradeId", grade.id).addExtra("gradesSubjectId", grade.subjectId)
}
for (notice in app.db.noticeDao().getNotNotifiedNow(profileId)) {
val noticeTypeStr = if (notice.type == Notice.TYPE_POSITIVE) app.getString(R.string.notification_notice_praise) else if (notice.type == Notice.TYPE_NEGATIVE) app.getString(R.string.notification_notice_warning) else app.getString(R.string.notification_notice_new)
val text = app.getString(R.string.notification_notice_format, noticeTypeStr, notice.teacherFullName, Date.fromMillis(notice.addedDate).formattedString)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_NOTICE),
text = text,
type = TYPE_NEW_NOTICE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_BEHAVIOUR,
addedDate = notice.addedDate
).addExtra("noticeId", notice.id)
}
for (attendance in app.db.attendanceDao().getNotNotifiedNow(profileId)) {
var attendanceTypeStr = app.getString(R.string.notification_type_attendance)
when (attendance.type) {
Attendance.TYPE_ABSENT -> attendanceTypeStr = app.getString(R.string.notification_absence)
Attendance.TYPE_ABSENT_EXCUSED -> attendanceTypeStr = app.getString(R.string.notification_absence_excused)
Attendance.TYPE_BELATED -> attendanceTypeStr = app.getString(R.string.notification_belated)
Attendance.TYPE_BELATED_EXCUSED -> attendanceTypeStr = app.getString(R.string.notification_belated_excused)
Attendance.TYPE_RELEASED -> attendanceTypeStr = app.getString(R.string.notification_release)
}
val text = app.getString(
if (attendance.subjectLongName.isNullOrEmpty())
R.string.notification_attendance_no_lesson_format
else
R.string.notification_attendance_format,
attendanceTypeStr,
attendance.subjectLongName,
attendance.lessonDate.formattedString
)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_ATTENDANCE),
text = text,
type = TYPE_NEW_ATTENDANCE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_ATTENDANCE,
addedDate = attendance.addedDate
).addExtra("attendanceId", attendance.id).addExtra("attendanceSubjectId", attendance.subjectId)
}
for (announcement in app.db.announcementDao().getNotNotifiedNow(profileId)) {
val text = app.context.getString(R.string.notification_announcement_format, announcement.subject)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_ANNOUNCEMENT),
text = text,
type = TYPE_NEW_ANNOUNCEMENT,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_ANNOUNCEMENTS,
addedDate = announcement.addedDate
).addExtra("announcementId", announcement.id)
}
for (message in app.db.messageDao().getReceivedNotNotifiedNow(profileId)) {
val text = app.context.getString(R.string.notification_message_format, message.senderFullName, message.subject)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_MESSAGE),
text = text,
type = TYPE_NEW_MESSAGE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_MESSAGES,
addedDate = message.addedDate
).addExtra("messageType", Message.TYPE_RECEIVED.toLong()).addExtra("messageId", message.id)
}
val luckyNumbers = app.db.luckyNumberDao().getNotNotifiedNow(profileId)
luckyNumbers?.removeAll { it.date < today }
luckyNumbers?.forEach { luckyNumber ->
val text = when {
luckyNumber.date.value == todayValue -> // LN for today
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_format else R.string.notification_lucky_number_format, luckyNumber.number)
luckyNumber.date.value == todayValue + 1 -> // LN for tomorrow
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_tomorrow_format else R.string.notification_lucky_number_tomorrow_format, luckyNumber.number)
else -> // LN for later
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_later_format else R.string.notification_lucky_number_later_format, luckyNumber.date.formattedString, luckyNumber.number)
}
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_LUCKY_NUMBER),
text = text,
type = TYPE_LUCKY_NUMBER,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_HOME,
addedDate = luckyNumber.addedDate
)
}
data.db.metadataDao().setAllNotified(profileId, true)
}}
}

View File

@ -126,3 +126,4 @@ const val EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN = 902
const val EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN = 903
const val EXCEPTION_LIBRUS_API_REQUEST = 904
const val EXCEPTION_MOBIDZIENNIK_WEB_REQUEST = 905
const val EXCEPTION_NOTIFY_AND_SYNC = 910

View File

@ -0,0 +1,179 @@
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.App.APP_URL
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.data.api.AppError.CODE_APP_SERVER_ERROR
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_SHARED_EVENT
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_SHARED_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.getNotificationTitle
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.network.ServerRequest
class ServerSync(val data: Data, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "ServerSync"
}
val app = data.app
val profileId = data.profile?.id ?: -1
val profileName = data.profile?.name ?: ""
val profile = data.profile
val loginStore = data.loginStore
private fun getUsernameId(): String {
if (loginStore.data == null) {
return "NO_LOGIN_STORE"
}
if (profile?.studentData == null) {
return "NO_STUDENT_STORE"
}
return when (data.loginStore.type) {
LoginStore.LOGIN_TYPE_MOBIDZIENNIK -> loginStore.getLoginData("serverName", "MOBI_UN") + ":" + loginStore.getLoginData("username", "MOBI_UN") + ":" + profile.getStudentData("studentId", -1)
LoginStore.LOGIN_TYPE_LIBRUS -> profile.getStudentData("schoolName", "LIBRUS_UN") + ":" + profile.getStudentData("accountLogin", "LIBRUS_LOGIN_UN")
LoginStore.LOGIN_TYPE_IUCZNIOWIE -> loginStore.getLoginData("schoolName", "IUCZNIOWIE_UN") + ":" + loginStore.getLoginData("username", "IUCZNIOWIE_UN") + ":" + profile.getStudentData("registerId", -1)
LoginStore.LOGIN_TYPE_VULCAN -> profile.getStudentData("schoolName", "VULCAN_UN") + ":" + profile.getStudentData("studentId", -1)
LoginStore.LOGIN_TYPE_DEMO -> loginStore.getLoginData("serverName", "DEMO_UN") + ":" + loginStore.getLoginData("username", "DEMO_UN") + ":" + profile.getStudentData("studentId", -1)
else -> "TYPE_UNKNOWN"
}
}
init { run {
if (profile?.registration != Profile.REGISTRATION_ENABLED) {
onSuccess()
return@run
}
val request = ServerRequest(
app,
app.requestScheme+APP_URL+"main.php?sync",
"Edziennik2/REG",
profile,
data.loginStore.type,
getUsernameId()
)
if (profile.empty) {
request.setBodyParameter("first_run", "true")
}
var hasNotifications = true
if (app.appConfig.webPushEnabled) {
data.notifications
.filterNot { it.posted }
.let {
if (it.isEmpty()) {
hasNotifications = false
null
}
else
it
}?.forEachIndexed { index, notification ->
if (notification.type != TYPE_NEW_SHARED_EVENT
&& notification.type != TYPE_SERVER_MESSAGE
&& notification.type != TYPE_NEW_SHARED_HOMEWORK) {
request.setBodyParameter("notify[$index][type]", notification.type.toString())
request.setBodyParameter("notify[$index][title]", notification.title)
request.setBodyParameter("notify[$index][text]", notification.text)
}
}
}
if ((!app.appConfig.webPushEnabled || !hasNotifications) && !profile.enableSharedEvents) {
onSuccess()
return@run
}
val result = request.runSync()
if (result == null) {
data.error(ApiError(TAG, CODE_APP_SERVER_ERROR)
.setCritical(false))
onSuccess()
return@run
}
var apiResponse = result.toString()
if (result.getString("success") != "true") {
data.error(ApiError(TAG, CODE_APP_SERVER_ERROR)
.setCritical(false))
onSuccess()
return@run
}
// HERE PROCESS ALL THE RECEIVED EVENTS
// add them to the profile and create appropriate notifications
result.getJsonArray("events")?.forEach { jEventEl ->
val event = jEventEl.asJsonObject
val teamCode = event.getString("team")
// get the target Team from teamCode
val team = app.db.teamDao().getByCodeNow(profile.id, teamCode)
if (team != null) {
// create the event from Json. Add the missing teamId and !!profileId!!
val eventObject = app.gson.fromJson(event.toString(), Event::class.java)
// proguard. disable for Event.class
if (eventObject.eventDate == null) {
apiResponse += "\n\nEventDate == null\n$event"
}
eventObject.profileId = profileId
eventObject.teamId = team.id
eventObject.addedManually = true
if (eventObject.sharedBy == getUsernameId()) {
eventObject.sharedBy = "self"
eventObject.sharedByName = profile.studentNameLong
}
val typeObject = app.db.eventTypeDao().getByIdNow(profileId, eventObject.type)
app.db.eventDao().add(eventObject)
val metadata = Metadata(
profileId,
if (eventObject.type == TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT,
eventObject.id,
profile.empty,
true,
event.getLong("addedDate") ?: 0
)
val metadataId = app.db.metadataDao().add(metadata)
// notify if the event is new and not first sync
if (metadataId != -1L && !profile.empty) {
val text = app.getString(
R.string.notification_shared_event_format,
eventObject.sharedByName,
if (typeObject != null) typeObject.name else "wydarzenie",
if (eventObject.eventDate == null) "???" else eventObject.eventDate.formattedString,
eventObject.topic
)
val type = if (eventObject.type == TYPE_HOMEWORK) TYPE_NEW_SHARED_HOMEWORK else TYPE_NEW_SHARED_EVENT
data.notifications += Notification(
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = profileId,
profileName = profileName,
viewId = if (eventObject.type == TYPE_HOMEWORK) DRAWER_ITEM_HOMEWORK else DRAWER_ITEM_AGENDA,
addedDate = metadata.addedDate
).addExtra("eventId", eventObject.id).addExtra("eventDate", eventObject.eventDate.value.toLong())
}
}
}
onSuccess()
}}
}

View File

@ -1,9 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events
import pl.szczodrzynski.edziennik.api.v2.models.ApiTask
class ErrorReportTask : ApiTask(-1)

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events.task
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.ApiService
import pl.szczodrzynski.edziennik.api.v2.EdziennikNotification
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.ApiTask
import pl.szczodrzynski.edziennik.utils.Utils
class ErrorReportTask : ApiTask(-1) {
fun run(notification: EdziennikNotification, errorList: MutableList<ApiError>) {
notification
.setCurrentTask(taskId, null)
.setProgressRes(R.string.edziennik_notification_api_error_report_title)
.post()
errorList.forEach { error ->
Utils.d(ApiService.TAG, "Error ${error.tag} profile ${error.profileId}: code ${error.errorCode}")
}
errorList.clear()
notification.setIdle().post()
}
}

View File

@ -0,0 +1,79 @@
package pl.szczodrzynski.edziennik.api.v2.events.task
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.Notifier.ID_NOTIFICATIONS
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.models.ApiTask
import pl.szczodrzynski.edziennik.utils.models.Notification
import kotlin.math.min
class NotifyTask : ApiTask(-1) {
fun run(app: App) {
val list = app.db.notificationDao().getNotPostedNow()
val notificationList = list.subList(0, min(8, list.size))
var unreadCount = list.size
for (notification in notificationList) {
val intent = Intent(app, MainActivity::class.java)
notification.fillIntent(intent)
val pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0)
val notificationBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
// title, text, type, date
.setContentTitle(notification.title)
.setContentText(notification.text)
.setSubText(Notification.stringType(app, notification.type))
.setWhen(notification.addedDate)
.setTicker(app.getString(R.string.notification_ticker_format, Notification.stringType(app, notification.type)))
// icon, color, lights, priority
.setSmallIcon(R.drawable.ic_notification)
.setColor(app.notifier.notificationColor)
.setLights(-0xff0001, 2000, 2000)
.setPriority(app.notifier.notificationPriority)
// channel, group, style
.setChannelId(app.notifier.notificationGroup)
.setGroup(app.notifier.notificationGroup)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setStyle(NotificationCompat.BigTextStyle().bigText(notification.text))
// intent, auto cancel
.setContentIntent(pendingIntent)
.setAutoCancel(true)
if (!app.notifier.shouldBeQuiet()) {
notificationBuilder.setDefaults(app.notifier.notificationDefaults)
}
app.notifier.notificationManager.notify(notification.id, notificationBuilder.build())
}
if (notificationList.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val intent = Intent(app, MainActivity::class.java)
intent.action = "android.intent.action.MAIN"
intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS)
val pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS,
intent, 0)
val groupBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
.setSmallIcon(R.drawable.ic_notification)
.setColor(app.notifier.notificationColor)
.setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount))
.setGroupSummary(true)
.setAutoCancel(true)
.setChannelId(app.notifier.notificationGroup)
.setGroup(app.notifier.notificationGroup)
.setLights(-0xff0001, 2000, 2000)
.setPriority(app.notifier.notificationPriority)
.setContentIntent(pendingIntent)
.setStyle(NotificationCompat.BigTextStyle())
if (!app.notifier.shouldBeQuiet()) {
groupBuilder.setDefaults(app.notifier.notificationDefaults)
}
app.notifier.notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build())
}
app.db.notificationDao().setAllPosted()
}
}

View File

@ -36,8 +36,10 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
private fun completed() {
data.saveData()
data.notifyAndSyncEvents {
callback.onCompleted()
}
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |

View File

@ -36,8 +36,10 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
private fun completed() {
data.saveData()
data.notifyAndSyncEvents {
callback.onCompleted()
}
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |

View File

@ -5,8 +5,12 @@ import android.util.SparseArray
import com.google.gson.JsonObject
import im.wangchao.mhttp.Response
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.DataNotifications
import pl.szczodrzynski.edziennik.api.v2.EXCEPTION_NOTIFY_AND_SYNC
import pl.szczodrzynski.edziennik.api.v2.ServerSync
import pl.szczodrzynski.edziennik.api.v2.interfaces.EndpointCallback
import pl.szczodrzynski.edziennik.data.api.AppError.*
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.modules.announcements.Announcement
import pl.szczodrzynski.edziennik.data.db.modules.api.EndpointTimer
import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance
@ -23,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
@ -38,6 +43,9 @@ import java.net.UnknownHostException
import javax.net.ssl.SSLException
open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore) {
companion object {
private const val TAG = "Data"
}
var fakeLogin = false
@ -85,6 +93,8 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
var endpointTimers = mutableListOf<EndpointTimer>()
val notifications = mutableListOf<Notification>()
val teacherList = LongSparseArray<Teacher>()
val subjectList = LongSparseArray<Subject>()
val teamList = LongSparseArray<Team>()
@ -132,7 +142,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
val metadataList = mutableListOf<Metadata>()
val messageMetadataList = mutableListOf<Metadata>()
val db by lazy { app.db }
val db: AppDb by lazy { app.db }
init {
clear()
@ -175,6 +185,8 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
if (profile == null)
return // return on first login
profile.empty = false
db.profileDao().add(profile)
db.loginStoreDao().add(loginStore)
@ -233,6 +245,20 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
db.metadataDao().setSeen(messageMetadataList)
}
fun notifyAndSyncEvents(onSuccess: () -> Unit) {
try {
DataNotifications(this)
ServerSync(this) {
db.notificationDao().addAll(notifications)
onSuccess()
}
}
catch (e: Exception) {
error(ApiError(TAG, EXCEPTION_NOTIFY_AND_SYNC)
.withThrowable(e))
}
}
fun setSyncNext(endpointId: Int, syncIn: Long? = null, viewId: Int? = null) {
EndpointTimer(profile?.id ?: -1, endpointId).apply {
syncedNow()
@ -257,7 +283,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
}
fun error(tag: String, errorCode: Int, response: Response? = null, throwable: Throwable? = null, apiResponse: JsonObject? = null) {
var code = when (throwable) {
val code = when (throwable) {
is UnknownHostException, is SSLException, is InterruptedIOException -> CODE_NO_INTERNET
is SocketTimeoutException -> CODE_TIMEOUT
else -> when (response?.code()) {
@ -268,7 +294,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
error(ApiError(tag, code).apply { profileId = profile?.id ?: -1 }.withResponse(response).withThrowable(throwable).withApiResponse(apiResponse))
}
fun error(tag: String, errorCode: Int, response: Response? = null, apiResponse: String? = null) {
var code = when (null) {
val code = when (null) {
is UnknownHostException, is SSLException, is InterruptedIOException -> CODE_NO_INTERNET
is SocketTimeoutException -> CODE_TIMEOUT
else -> when (response?.code()) {

View File

@ -35,8 +35,10 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
private fun completed() {
data.saveData()
data.notifyAndSyncEvents {
callback.onCompleted()
}
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |

View File

@ -35,8 +35,10 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
private fun completed() {
data.saveData()
data.notifyAndSyncEvents {
callback.onCompleted()
}
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |

View File

@ -40,8 +40,8 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.BuildConfig;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.WidgetTimetable;
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface;
import pl.szczodrzynski.edziennik.data.api.interfaces.SyncCallback;
@ -62,11 +62,11 @@ import pl.szczodrzynski.edziennik.data.db.modules.notices.NoticeFull;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Notification;
import pl.szczodrzynski.edziennik.network.ServerRequest;
import pl.szczodrzynski.edziennik.sync.SyncJob;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Notification;
import pl.szczodrzynski.edziennik.widgets.luckynumber.WidgetLuckyNumber;
import pl.szczodrzynski.edziennik.widgets.notifications.WidgetNotifications;
@ -102,6 +102,19 @@ import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_VULCAN;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_AUTO_ARCHIVING;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_LUCKY_NUMBER;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_ANNOUNCEMENT;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_ATTENDANCE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_EVENT;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_GRADE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_HOMEWORK;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_MESSAGE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_NOTICE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_SHARED_EVENT;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_SHARED_HOMEWORK;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_SERVER_MESSAGE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_TIMETABLE_LESSON_CHANGE;
import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.REGISTRATION_ENABLED;
import static pl.szczodrzynski.edziennik.sync.SyncService.PROFILE_MAX_PROGRESS;
import static pl.szczodrzynski.edziennik.utils.Utils.d;
@ -291,7 +304,7 @@ public class Edziennik {
String text = app.getContext().getString(R.string.notification_lesson_change_format, change.changeTypeStr(app.getContext()), change.lessonDate == null ? "" : change.lessonDate.getFormattedString(), change.subjectLongName);
app.notifier.add(new Notification(app.getContext(), text)
.withProfileData(profile.getId(), profile.getName())
.withType(Notification.TYPE_TIMETABLE_LESSON_CHANGE)
.withType(TYPE_TIMETABLE_LESSON_CHANGE)
.withFragmentRedirect(MainActivity.DRAWER_ITEM_TIMETABLE)
.withLongExtra("timetableDate", change.lessonDate.getValue())
.withAddedDate(change.addedDate)
@ -305,7 +318,7 @@ public class Edziennik {
text = app.getContext().getString(R.string.notification_event_format, event.typeName, event.eventDate.getFormattedString(), ns(app.getString(R.string.notification_event_no_subject), event.subjectLongName));
app.notifier.add(new Notification(app.getContext(), text)
.withProfileData(profile.getId(), profile.getName())
.withType(event.type == TYPE_HOMEWORK ? Notification.TYPE_NEW_HOMEWORK : Notification.TYPE_NEW_EVENT)
.withType(event.type == TYPE_HOMEWORK ? TYPE_NEW_HOMEWORK : TYPE_NEW_EVENT)
.withFragmentRedirect(event.type == TYPE_HOMEWORK ? MainActivity.DRAWER_ITEM_HOMEWORK : MainActivity.DRAWER_ITEM_AGENDA)
.withLongExtra("eventId", event.id)
.withLongExtra("eventDate", event.eventDate.getValue())
@ -342,7 +355,7 @@ public class Edziennik {
String text = app.getContext().getString(R.string.notification_grade_format, gradeName, grade.subjectLongName);
app.notifier.add(new Notification(app.getContext(), text)
.withProfileData(profile.getId(), profile.getName())
.withType(Notification.TYPE_NEW_GRADE)
.withType(TYPE_NEW_GRADE)
.withFragmentRedirect(MainActivity.DRAWER_ITEM_GRADES)
.withLongExtra("gradesSubjectId", grade.subjectId)
.withAddedDate(grade.addedDate)
@ -353,7 +366,7 @@ public class Edziennik {
String text = app.getContext().getString(R.string.notification_notice_format, noticeTypeStr, notice.teacherFullName, Date.fromMillis(notice.addedDate).getFormattedString());
app.notifier.add(new Notification(app.getContext(), text)
.withProfileData(profile.getId(), profile.getName())
.withType(Notification.TYPE_NEW_NOTICE)
.withType(TYPE_NEW_NOTICE)
.withFragmentRedirect(MainActivity.DRAWER_ITEM_BEHAVIOUR)
.withLongExtra("noticeId", notice.id)
.withAddedDate(notice.addedDate)
@ -381,7 +394,7 @@ public class Edziennik {
String text = app.getContext().getString(R.string.notification_attendance_format, attendanceTypeStr, attendance.subjectLongName, attendance.lessonDate.getFormattedString());
app.notifier.add(new Notification(app.getContext(), text)
.withProfileData(profile.getId(), profile.getName())
.withType(Notification.TYPE_NEW_ATTENDANCE)
.withType(TYPE_NEW_ATTENDANCE)
.withFragmentRedirect(MainActivity.DRAWER_ITEM_ATTENDANCE)
.withLongExtra("attendanceId", attendance.id)
.withAddedDate(attendance.addedDate)
@ -391,7 +404,7 @@ public class Edziennik {
String text = app.getContext().getString(R.string.notification_announcement_format, announcement.subject);
app.notifier.add(new Notification(app.getContext(), text)
.withProfileData(profile.getId(), profile.getName())
.withType(Notification.TYPE_NEW_ANNOUNCEMENT)
.withType(TYPE_NEW_ANNOUNCEMENT)
.withFragmentRedirect(MainActivity.DRAWER_ITEM_ANNOUNCEMENTS)
.withLongExtra("announcementId", announcement.id)
.withAddedDate(announcement.addedDate)
@ -401,7 +414,7 @@ public class Edziennik {
String text = app.getContext().getString(R.string.notification_message_format, message.senderFullName, message.subject);
app.notifier.add(new Notification(app.getContext(), text)
.withProfileData(profile.getId(), profile.getName())
.withType(Notification.TYPE_NEW_MESSAGE)
.withType(TYPE_NEW_MESSAGE)
.withFragmentRedirect(MainActivity.DRAWER_ITEM_MESSAGES)
.withLongExtra("messageType", Message.TYPE_RECEIVED)
.withLongExtra("messageId", message.id)
@ -423,7 +436,7 @@ public class Edziennik {
}
app.notifier.add(new Notification(app.getContext(), text)
.withProfileData(profile.getId(), profile.getName())
.withType(Notification.TYPE_LUCKY_NUMBER)
.withType(TYPE_LUCKY_NUMBER)
.withFragmentRedirect(MainActivity.DRAWER_ITEM_HOME)
);
oldLuckyNumber = profile.getLuckyNumber();
@ -456,9 +469,9 @@ public class Edziennik {
for (Notification notification : app.appConfig.notifications) {
//Log.d(TAG, notification.text);
if (!notification.notified) {
if (notification.type != Notification.TYPE_NEW_SHARED_EVENT
&& notification.type != Notification.TYPE_SERVER_MESSAGE
&& notification.type != Notification.TYPE_NEW_SHARED_HOMEWORK) // these are automatically sent to the browser by the server
if (notification.type != TYPE_NEW_SHARED_EVENT
&& notification.type != TYPE_SERVER_MESSAGE
&& notification.type != TYPE_NEW_SHARED_HOMEWORK) // these are automatically sent to the browser by the server
{
//Log.d(TAG, "Adding notify[" + position + "]");
syncRequest.setBodyParameter("notify[" + position + "][type]", Integer.toString(notification.type));
@ -520,7 +533,7 @@ public class Edziennik {
if (metadataId != -1 && !registerEmpty) {
app.notifier.add(new Notification(app.getContext(), app.getString(R.string.notification_shared_event_format, event.sharedByName, type != null ? type.name : "wydarzenie", event.eventDate == null ? "nieznana data" : event.eventDate.getFormattedString(), event.topic))
.withProfileData(profile.getId(), profile.getName())
.withType(event.type == TYPE_HOMEWORK ? Notification.TYPE_NEW_SHARED_HOMEWORK : Notification.TYPE_NEW_SHARED_EVENT)
.withType(event.type == TYPE_HOMEWORK ? TYPE_NEW_SHARED_HOMEWORK : TYPE_NEW_SHARED_EVENT)
.withFragmentRedirect(event.type == TYPE_HOMEWORK ? MainActivity.DRAWER_ITEM_HOMEWORK : MainActivity.DRAWER_ITEM_AGENDA)
.withLongExtra("eventDate", event.eventDate.getValue())
);
@ -687,7 +700,7 @@ public class Edziennik {
profile.setArchived(true);
app.notifier.add(new Notification(app.getContext(), app.getString(R.string.profile_auto_archiving_format, profile.getName(), profile.getDateYearEnd().getFormattedString()))
.withProfileData(profile.getId(), profile.getName())
.withType(Notification.TYPE_AUTO_ARCHIVING)
.withType(TYPE_AUTO_ARCHIVING)
.withFragmentRedirect(DRAWER_ITEM_HOME)
.withLongExtra("autoArchiving", 1L)
);

View File

@ -52,6 +52,8 @@ import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata;
import pl.szczodrzynski.edziennik.data.db.modules.metadata.MetadataDao;
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice;
import pl.szczodrzynski.edziennik.data.db.modules.notices.NoticeDao;
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification;
import pl.szczodrzynski.edziennik.data.db.modules.notification.NotificationDao;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileDao;
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject;
@ -88,7 +90,8 @@ import pl.szczodrzynski.edziennik.utils.models.Date;
DebugLog.class,
EndpointTimer.class,
LessonRange.class,
Metadata.class}, version = 59)
Notification.class,
Metadata.class}, version = 60)
@TypeConverters({
ConverterTime.class,
ConverterDate.class,
@ -121,6 +124,7 @@ public abstract class AppDb extends RoomDatabase {
public abstract DebugLogDao debugLogDao();
public abstract EndpointTimerDao endpointTimerDao();
public abstract LessonRangeDao lessonRangeDao();
public abstract NotificationDao notificationDao();
public abstract MetadataDao metadataDao();
private static volatile AppDb INSTANCE;
@ -644,6 +648,25 @@ public abstract class AppDb extends RoomDatabase {
database.execSQL("DROP TABLE _old_luckyNumbers;");
}
};
private static final Migration MIGRATION_59_60 = new Migration(59, 60) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE notifications (\n" +
" id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" +
" title TEXT NOT NULL,\n" +
" `text` TEXT NOT NULL,\n" +
" `type` INTEGER NOT NULL,\n" +
" profileId INTEGER DEFAULT NULL,\n" +
" profileName TEXT DEFAULT NULL,\n" +
" posted INTEGER NOT NULL DEFAULT 0,\n" +
" viewId INTEGER DEFAULT NULL,\n" +
" extras TEXT DEFAULT NULL,\n" +
" addedDate INTEGER NOT NULL\n" +
");");
database.execSQL("ALTER TABLE profiles ADD COLUMN disabledNotifications TEXT DEFAULT NULL");
}
};
public static AppDb getDatabase(final Context context) {
@ -700,7 +723,8 @@ public abstract class AppDb extends RoomDatabase {
MIGRATION_55_56,
MIGRATION_56_57,
MIGRATION_57_58,
MIGRATION_58_59
MIGRATION_58_59,
MIGRATION_59_60
)
.allowMainThreadQueries()
//.fallbackToDestructiveMigration()

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-18.
*/
package pl.szczodrzynski.edziennik.data.db.modules.notification
import android.content.Context
import android.content.Intent
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_AUTO_ARCHIVING
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_ERROR
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_FEEDBACK_MESSAGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_GENERAL
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_ANNOUNCEMENT
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_ATTENDANCE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_EVENT
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_GRADE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_MESSAGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_NOTICE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_SHARED_EVENT
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_TIMETABLE_CHANGED
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_TIMETABLE_LESSON_CHANGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_UPDATE
@Entity(tableName = "notifications")
data class Notification(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val text: String,
val type: Int,
val profileId: Int?,
val profileName: String?,
var posted: Boolean = false,
var viewId: Int? = null,
var extras: JsonObject? = null,
val addedDate: Long = System.currentTimeMillis()
) {
companion object {
const val TYPE_GENERAL = 0
const val TYPE_UPDATE = 1
const val TYPE_ERROR = 2
const val TYPE_TIMETABLE_CHANGED = 3
const val TYPE_TIMETABLE_LESSON_CHANGE = 4
const val TYPE_NEW_GRADE = 5
const val TYPE_NEW_EVENT = 6
const val TYPE_NEW_HOMEWORK = 10
const val TYPE_NEW_SHARED_EVENT = 7
const val TYPE_NEW_SHARED_HOMEWORK = 12
const val TYPE_NEW_MESSAGE = 8
const val TYPE_NEW_NOTICE = 9
const val TYPE_NEW_ATTENDANCE = 13
const val TYPE_SERVER_MESSAGE = 11
const val TYPE_LUCKY_NUMBER = 14
const val TYPE_NEW_ANNOUNCEMENT = 15
const val TYPE_FEEDBACK_MESSAGE = 16
const val TYPE_AUTO_ARCHIVING = 17
}
fun addExtra(key: String, value: Long?): Notification {
extras = extras ?: JsonObject()
extras?.addProperty(key, value)
return this
}
fun addExtra(key: String, value: String?): Notification {
extras = extras ?: JsonObject()
extras?.addProperty(key, value)
return this
}
fun fillIntent(intent: Intent) {
if (profileId != -1)
intent.putExtra("profileId", profileId)
if (viewId != -1)
intent.putExtra("fragmentId", viewId)
try {
extras?.entrySet()?.forEach { (key, value) ->
if (!value.isJsonPrimitive)
return@forEach
val primitive = value.asJsonPrimitive
if (primitive.isNumber) {
intent.putExtra(key, primitive.asLong)
} else if (primitive.isString) {
intent.putExtra(key, primitive.asString)
}
}
} catch (e: NullPointerException) {
e.printStackTrace()
}
}
}
fun Context.getNotificationTitle(type: Int): String {
return getString(when (type) {
TYPE_UPDATE -> R.string.notification_type_update
TYPE_ERROR -> R.string.notification_type_error
TYPE_TIMETABLE_CHANGED -> R.string.notification_type_timetable_change
TYPE_TIMETABLE_LESSON_CHANGE -> R.string.notification_type_timetable_lesson_change
TYPE_NEW_GRADE -> R.string.notification_type_new_grade
TYPE_NEW_EVENT -> R.string.notification_type_new_event
TYPE_NEW_HOMEWORK -> R.string.notification_type_new_homework
TYPE_NEW_SHARED_EVENT -> R.string.notification_type_new_shared_event
TYPE_NEW_MESSAGE -> R.string.notification_type_new_message
TYPE_NEW_NOTICE -> R.string.notification_type_notice
TYPE_NEW_ATTENDANCE -> R.string.notification_type_attendance
TYPE_SERVER_MESSAGE -> R.string.notification_type_server_message
TYPE_LUCKY_NUMBER -> R.string.notification_type_lucky_number
TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message
TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement
TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving
TYPE_GENERAL -> R.string.notification_type_general
else -> R.string.notification_type_general
})
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-18.
*/
package pl.szczodrzynski.edziennik.data.db.modules.notification
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface NotificationDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(notification: Notification)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addAll(notificationList: List<Notification>)
@Query("DELETE FROM notifications WHERE profileId = :profileId")
fun clear(profileId: Int)
@Query("SELECT * FROM notifications")
fun getAll(): LiveData<List<Notification>>
@Query("SELECT * FROM notifications")
fun getAllNow(): List<Notification>
@Query("SELECT * FROM notifications WHERE posted = 0 ORDER BY addedDate DESC")
fun getNotPostedNow(): List<Notification>
@Query("UPDATE notifications SET posted = 1 WHERE posted = 0")
fun setAllPosted()
}

View File

@ -84,6 +84,8 @@ open class Profile : IDrawerProfile {
var changedEndpoints: List<String>? = null
var disabledNotifications: List<Long>? = null
var lastFullSync: Long = 0
var lastReceiversSync: Long = 0

View File

@ -16,6 +16,7 @@ import im.wangchao.mhttp.ThreadMode;
import im.wangchao.mhttp.callback.JsonCallbackHandler;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.BuildConfig;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.utils.Utils;
@ -33,21 +34,25 @@ public class ServerRequest {
}
public ServerRequest(App app, String url, String source, ProfileFull profileFull) {
this(app, url, source, profileFull, profileFull == null ? -1 : profileFull.getLoginStoreType(), profileFull == null ? "" : profileFull.getUsernameId());
}
public ServerRequest(App app, String url, String source, Profile profile, int loginStoreType, String usernameId) {
this.app = app;
this.url = url;
this.params = new ArrayList<>();
this.username = (profileFull != null && profileFull.getRegistration() == REGISTRATION_ENABLED ? profileFull.getUsernameId() : app.deviceId);
this.username = (profile != null && profile.getRegistration() == REGISTRATION_ENABLED ? usernameId : app.deviceId);
this.source = source;
if (profileFull != null && profileFull.getRegistration() == REGISTRATION_ENABLED) {
this.setBodyParameter("login_type", Integer.toString(profileFull.getLoginStoreType()));
this.setBodyParameter("name_long", profileFull.getStudentNameLong());
this.setBodyParameter("name_short", profileFull.getStudentNameShort());
if (profile != null && profile.getRegistration() == REGISTRATION_ENABLED) {
this.setBodyParameter("login_type", Integer.toString(loginStoreType));
this.setBodyParameter("name_long", profile.getStudentNameLong());
this.setBodyParameter("name_short", profile.getStudentNameShort());
//if (Looper.myLooper() == Looper.getMainLooper()) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
this.setBodyParameter("team_ids", "UI_THREAD");
}
else {
this.setBodyParameter("team_ids", app.gson.toJson(app.db.teamDao().getAllCodesNow(profileFull.getId())));
this.setBodyParameter("team_ids", app.gson.toJson(app.db.teamDao().getAllCodesNow(profile.getId())));
}
}
}

View File

@ -15,21 +15,25 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.BuildConfig;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.modules.events.Event;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType;
import pl.szczodrzynski.edziennik.data.db.modules.feedback.FeedbackMessage;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team;
import pl.szczodrzynski.edziennik.network.ServerRequest;
import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment;
import pl.szczodrzynski.edziennik.utils.models.Notification;
import pl.szczodrzynski.edziennik.network.ServerRequest;
import static pl.szczodrzynski.edziennik.App.APP_URL;
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_FEEDBACK_MESSAGE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_SHARED_EVENT;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_SHARED_HOMEWORK;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_SERVER_MESSAGE;
import static pl.szczodrzynski.edziennik.utils.Utils.d;
import static pl.szczodrzynski.edziennik.utils.Utils.strToInt;
@ -175,7 +179,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
case "message":
app.notifier.add(new Notification(app.getContext(), remoteMessage.getData().get("message"))
.withTitle(remoteMessage.getData().get("title"))
.withType(Notification.TYPE_SERVER_MESSAGE)
.withType(TYPE_SERVER_MESSAGE)
.withFragmentRedirect(MainActivity.DRAWER_ITEM_NOTIFICATIONS)
);
app.notifier.postAll(null);
@ -203,7 +207,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
app.notifier.add(new Notification(app.getContext(), feedbackMessage.text)
.withTitle(remoteMessage.getData().get("title"))
.withType(Notification.TYPE_FEEDBACK_MESSAGE)
.withType(TYPE_FEEDBACK_MESSAGE)
.withFragmentRedirect(MainActivity.TARGET_FEEDBACK)
);
app.notifier.postAll(null);
@ -225,7 +229,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
});
app.notifier.add(new Notification(app.getContext(), remoteMessage.getData().get("message"))
.withTitle(remoteMessage.getData().get("title"))
.withType(Notification.TYPE_FEEDBACK_MESSAGE)
.withType(TYPE_FEEDBACK_MESSAGE)
.withFragmentRedirect(MainActivity.TARGET_FEEDBACK)
);
app.notifier.postAll(null);
@ -279,7 +283,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
EventType eventType = app.db.eventTypeDao().getByIdNow(profile.getId(), event.type);
app.notifier.add(new Notification(app.getContext(), app.getString((oldEvent == null ? R.string.notification_shared_event_format : R.string.notification_shared_event_modified_format), event.sharedByName, eventType == null ? "wydarzenie" : eventType.name, event.eventDate.getFormattedString(), event.topic))
.withProfileData(profile.getId(), profile.getName())
.withType(event.type == TYPE_HOMEWORK ? Notification.TYPE_NEW_SHARED_HOMEWORK : Notification.TYPE_NEW_SHARED_EVENT)
.withType(event.type == TYPE_HOMEWORK ? TYPE_NEW_SHARED_HOMEWORK : TYPE_NEW_SHARED_EVENT)
.withFragmentRedirect(event.type == TYPE_HOMEWORK ? MainActivity.DRAWER_ITEM_HOMEWORK : MainActivity.DRAWER_ITEM_AGENDA)
.withLongExtra("eventDate", event.eventDate.getValue())
);
@ -298,7 +302,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
if (oldEvent != null) {
app.notifier.add(new Notification(app.getContext(), app.getString(R.string.notification_shared_event_removed_format, oldEvent.sharedByName, oldEvent.typeName, oldEvent.eventDate.getFormattedString(), oldEvent.topic))
.withProfileData(profile.getId(), profile.getName())
.withType(oldEvent.type == TYPE_HOMEWORK ? Notification.TYPE_NEW_SHARED_HOMEWORK : Notification.TYPE_NEW_SHARED_EVENT)
.withType(oldEvent.type == TYPE_HOMEWORK ? TYPE_NEW_SHARED_HOMEWORK : TYPE_NEW_SHARED_EVENT)
.withFragmentRedirect(oldEvent.type == TYPE_HOMEWORK ? MainActivity.DRAWER_ITEM_HOMEWORK : MainActivity.DRAWER_ITEM_AGENDA)
.withLongExtra("eventDate", oldEvent.eventDate.getValue())
);

View File

@ -12,6 +12,24 @@ import java.util.Random;
import pl.szczodrzynski.edziennik.R;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_AUTO_ARCHIVING;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_ERROR;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_FEEDBACK_MESSAGE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_GENERAL;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_LUCKY_NUMBER;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_ANNOUNCEMENT;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_ATTENDANCE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_EVENT;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_GRADE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_HOMEWORK;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_MESSAGE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_NOTICE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_NEW_SHARED_EVENT;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_SERVER_MESSAGE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_TIMETABLE_CHANGED;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_TIMETABLE_LESSON_CHANGE;
import static pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.TYPE_UPDATE;
public class Notification {
public int profileId;
public String title;
@ -78,25 +96,6 @@ public class Notification {
return this;
}
public static final int TYPE_GENERAL = 0;
public static final int TYPE_UPDATE = 1;
public static final int TYPE_ERROR = 2;
public static final int TYPE_TIMETABLE_CHANGED = 3;
public static final int TYPE_TIMETABLE_LESSON_CHANGE = 4;
public static final int TYPE_NEW_GRADE = 5;
public static final int TYPE_NEW_EVENT = 6;
public static final int TYPE_NEW_HOMEWORK = 10;
public static final int TYPE_NEW_SHARED_EVENT = 7;
public static final int TYPE_NEW_SHARED_HOMEWORK = 12;
public static final int TYPE_NEW_MESSAGE = 8;
public static final int TYPE_NEW_NOTICE = 9;
public static final int TYPE_NEW_ATTENDANCE = 13;
public static final int TYPE_SERVER_MESSAGE = 11;
public static final int TYPE_LUCKY_NUMBER = 14;
public static final int TYPE_NEW_ANNOUNCEMENT = 15;
public static final int TYPE_FEEDBACK_MESSAGE = 16;
public static final int TYPE_AUTO_ARCHIVING = 17;
public static String stringType(Context context, int errorCode)
{
switch (errorCode) {

View File

@ -446,7 +446,8 @@
<string name="notification_absence">Absence</string>
<string name="notification_absence_excused">Excused absence</string>
<string name="notification_announcement_format">School announcement: %s</string>
<string name="notification_attendance_format">%s on lesson %s z %s</string>
<string name="notification_attendance_format">%1$s on lesson %2$s on day %3$s</string>
<string name="notification_attendance_no_lesson_format">%1$s on day %3$s</string>
<string name="notification_belated">Late</string>
<string name="notification_belated_excused">Excused late</string>
<string name="notification_channel_get_data_desc">Notification about data downloading</string>
@ -458,7 +459,8 @@
<string name="notification_channel_updates_desc">Notifications about new versions of the app</string>
<string name="notification_channel_updates_name">App updates</string>
<string name="notification_downloading_update">Downloading update…</string>
<string name="notification_event_format">%s %s from %s</string>
<string name="notification_event_format">%1$s on %2$s from %3$s</string>
<string name="notification_event_no_subject_format">%1$s on %2$s</string>
<string name="notification_event_no_subject">unknown subject</string>
<string name="notification_get_data_cancel">Cancel</string>
<string name="notification_get_data_error_summary">Szkolny.eu: error</string>
@ -469,6 +471,7 @@
<string name="notification_get_data_title">Update</string>
<string name="notification_grade_format">New grade (%s) from %s</string>
<string name="notification_homework_format">Homework from %s for %s</string>
<string name="notification_homework_no_subject_format">Homework for %s</string>
<string name="notification_lucky_number_format">Today %d is the lucky number.</string>
<string name="notification_lucky_number_later_format">The lucky number for %s is %d.</string>
<string name="notification_lucky_number_tomorrow_format">The lucky number for tomorrow is %d.</string>

View File

@ -487,7 +487,8 @@
<string name="notification_absence">Nieobecność</string>
<string name="notification_absence_excused">Nieobecność usprawiedliwiona</string>
<string name="notification_announcement_format">Ogłoszenie szkolne: %s</string>
<string name="notification_attendance_format">%s na lekcji %s z %s</string>
<string name="notification_attendance_format">%1$s na lekcji %2$s z dnia %3$s</string>
<string name="notification_attendance_no_lesson_format">%1$s z dnia %3$s</string>
<string name="notification_belated">Spóźnienie</string>
<string name="notification_belated_excused">Spóźnienie usprawiedliwione</string>
<string name="notification_channel_get_data_desc">Powiadomienie o pobieraniu danych dla e-dziennika</string>
@ -499,7 +500,8 @@
<string name="notification_channel_updates_desc">Powiadomienia o nowych wersjach aplikacji</string>
<string name="notification_channel_updates_name">Aktualizacje</string>
<string name="notification_downloading_update">Pobieranie aktualizacji…</string>
<string name="notification_event_format">%s %s z %s</string>
<string name="notification_event_format">%1$s dnia %2$s z %3$s</string>
<string name="notification_event_no_subject_format">%1$s dnia %2$s</string>
<string name="notification_event_no_subject">nieznanego przedmiotu</string>
<string name="notification_get_data_cancel">Przerwij</string>
<string name="notification_get_data_error_summary">Szkolny.eu: błąd</string>
@ -509,7 +511,8 @@
<string name="notification_get_data_text">Pobieranie danych</string>
<string name="notification_get_data_title">Synchronizacja</string>
<string name="notification_grade_format">Nowa ocena (%s) z %s</string>
<string name="notification_homework_format">Zadanie domowe z %s na %s</string>
<string name="notification_homework_format">Zadanie domowe z %1$s na %2$s</string>
<string name="notification_homework_no_subject_format">Zadanie domowe na %2$s</string>
<string name="notification_lesson_change_format" translatable="false">%s %s - %s</string>
<string name="notification_lucky_number_format">Dzisiaj %d to szczęśliwy numerek.</string>
<string name="notification_lucky_number_later_format">Szczęsliwy numerek na %s to %d.</string>