From 3f61ab8299c582dda78950a891efb26d969cebb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 29 Apr 2020 15:14:45 +0200 Subject: [PATCH 01/24] [DB] Refactor database and entities. --- .../java/pl/szczodrzynski/edziennik/App.kt | 1 + .../edziennik/data/api/Regexes.kt | 3 + .../data/web/EdudziennikWebAnnouncements.kt | 25 +-- .../data/web/EdudziennikWebAttendance.kt | 42 +++-- .../data/web/EdudziennikWebEvents.kt | 3 +- .../data/web/EdudziennikWebExams.kt | 5 +- .../data/web/EdudziennikWebGrades.kt | 12 +- .../data/web/EdudziennikWebHomework.kt | 5 +- .../data/web/EdudziennikWebLuckyNumber.kt | 9 +- .../data/web/EdudziennikWebNotes.kt | 18 +- .../data/web/EdudziennikWebTimetable.kt | 3 +- .../data/api/IdziennikApiCurrentRegister.kt | 9 +- .../data/api/IdziennikApiMessagesInbox.kt | 6 +- .../data/api/IdziennikApiMessagesSent.kt | 5 +- .../data/web/IdziennikWebAnnouncements.kt | 19 +- .../data/web/IdziennikWebAttendance.kt | 122 +++++++----- .../idziennik/data/web/IdziennikWebExams.kt | 5 +- .../data/web/IdziennikWebGetMessage.kt | 3 +- .../idziennik/data/web/IdziennikWebGrades.kt | 22 ++- .../data/web/IdziennikWebHomework.kt | 8 +- .../idziennik/data/web/IdziennikWebNotices.kt | 25 ++- .../data/web/IdziennikWebProposedGrades.kt | 32 ++-- .../data/web/IdziennikWebSendMessage.kt | 2 +- .../data/web/IdziennikWebTimetable.kt | 3 +- .../idziennik/login/IdziennikLoginWeb.kt | 9 +- .../api/LibrusApiAnnouncementMarkAsRead.kt | 3 +- .../librus/data/api/LibrusApiAnnouncements.kt | 23 +-- .../data/api/LibrusApiAttendanceTypes.kt | 34 +++- .../librus/data/api/LibrusApiAttendances.kt | 42 +++-- .../data/api/LibrusApiBehaviourGrades.kt | 18 +- .../data/api/LibrusApiDescriptiveGrades.kt | 6 +- .../librus/data/api/LibrusApiEvents.kt | 6 +- .../librus/data/api/LibrusApiGrades.kt | 6 +- .../librus/data/api/LibrusApiHomework.kt | 6 +- .../librus/data/api/LibrusApiLuckyNumber.kt | 9 +- .../librus/data/api/LibrusApiNotices.kt | 18 +- .../librus/data/api/LibrusApiPointGrades.kt | 6 +- .../librus/data/api/LibrusApiPtMeetings.kt | 3 +- .../data/api/LibrusApiTeacherFreeDays.kt | 21 +-- .../librus/data/api/LibrusApiTextGrades.kt | 6 +- .../librus/data/api/LibrusApiTimetables.kt | 3 +- .../data/messages/LibrusMessagesGetList.kt | 6 +- .../data/messages/LibrusMessagesGetMessage.kt | 3 +- .../messages/LibrusMessagesSendMessage.kt | 2 +- .../data/synergia/LibrusSynergiaHomework.kt | 8 +- .../data/api/MobidziennikApiAttendance.kt | 49 +++-- .../data/api/MobidziennikApiEvents.kt | 7 +- .../data/api/MobidziennikApiGrades.kt | 7 +- .../data/api/MobidziennikApiHomework.kt | 6 +- .../data/api/MobidziennikApiNotices.kt | 19 +- .../data/api/MobidziennikApiTimetable.kt | 5 +- .../web/MobidziennikLuckyNumberExtractor.kt | 9 +- .../data/web/MobidziennikWebAttendance.kt | 82 +++++--- .../data/web/MobidziennikWebCalendar.kt | 4 +- .../data/web/MobidziennikWebGetMessage.kt | 3 +- .../data/web/MobidziennikWebGrades.kt | 6 +- .../data/web/MobidziennikWebMessagesAll.kt | 5 +- .../data/web/MobidziennikWebMessagesInbox.kt | 6 +- .../data/web/MobidziennikWebMessagesSent.kt | 6 +- .../data/web/MobidziennikWebSendMessage.kt | 2 +- .../vulcan/data/api/VulcanApiAttendance.kt | 37 ++-- .../vulcan/data/api/VulcanApiDictionaries.kt | 32 +++- .../vulcan/data/api/VulcanApiEvents.kt | 5 +- .../vulcan/data/api/VulcanApiGrades.kt | 7 +- .../data/api/VulcanApiMessagesChangeStatus.kt | 3 +- .../vulcan/data/api/VulcanApiMessagesInbox.kt | 6 +- .../vulcan/data/api/VulcanApiMessagesSent.kt | 6 +- .../vulcan/data/api/VulcanApiNotices.kt | 24 ++- .../data/api/VulcanApiProposedGrades.kt | 3 +- .../vulcan/data/api/VulcanApiSendMessage.kt | 2 +- .../vulcan/data/api/VulcanApiTimetable.kt | 3 +- .../edziennik/data/api/models/Data.kt | 43 ++--- .../data/api/models/DataRemoveModel.kt | 8 +- .../edziennik/data/api/task/AppSync.kt | 3 +- .../edziennik/data/api/task/Notifications.kt | 28 +-- .../szczodrzynski/edziennik/data/db/AppDb.kt | 5 +- .../data/db/dao/AnnouncementDao.java | 82 -------- .../edziennik/data/db/dao/AnnouncementDao.kt | 69 +++++++ .../edziennik/data/db/dao/AttendanceDao.java | 108 ----------- .../edziennik/data/db/dao/AttendanceDao.kt | 74 ++++++++ .../edziennik/data/db/dao/BaseDao.kt | 23 ++- .../edziennik/data/db/dao/EventDao.kt | 7 +- .../edziennik/data/db/dao/GradeDao.kt | 139 +++++++------- .../edziennik/data/db/dao/LuckyNumberDao.java | 87 --------- .../edziennik/data/db/dao/LuckyNumberDao.kt | 74 ++++++++ .../edziennik/data/db/dao/MessageDao.kt | 10 +- .../edziennik/data/db/dao/MetadataDao.java | 42 ++--- .../edziennik/data/db/dao/NoticeDao.java | 82 -------- .../edziennik/data/db/dao/NoticeDao.kt | 72 +++++++ .../data/db/dao/TeacherAbsenceDao.kt | 99 +++++----- .../edziennik/data/db/dao/TimetableDao.kt | 173 +++++++---------- .../data/db/entity/Announcement.java | 58 ------ .../edziennik/data/db/entity/Announcement.kt | 40 ++++ .../edziennik/data/db/entity/Attendance.java | 65 ------- .../edziennik/data/db/entity/Attendance.kt | 68 +++++++ .../data/db/entity/AttendanceType.kt | 19 +- .../edziennik/data/db/entity/Event.kt | 3 +- .../edziennik/data/db/entity/Grade.kt | 26 +-- .../edziennik/data/db/entity/Lesson.kt | 12 +- .../edziennik/data/db/entity/LuckyNumber.java | 35 ---- .../edziennik/data/db/entity/LuckyNumber.kt | 22 +++ .../edziennik/data/db/entity/Message.kt | 5 +- .../edziennik/data/db/entity/Metadata.java | 5 +- .../edziennik/data/db/entity/Notice.java | 49 ----- .../edziennik/data/db/entity/Notice.kt | 43 +++++ .../data/db/entity/TeacherAbsence.kt | 58 +++--- .../data/db/full/AnnouncementFull.java | 16 -- .../data/db/full/AnnouncementFull.kt | 25 +++ .../data/db/full/AttendanceFull.java | 19 -- .../edziennik/data/db/full/AttendanceFull.kt | 28 +++ .../edziennik/data/db/full/EventFull.kt | 11 +- .../edziennik/data/db/full/GradeFull.kt | 10 +- .../edziennik/data/db/full/LessonFull.kt | 10 +- .../data/db/full/LuckyNumberFull.java | 15 -- .../edziennik/data/db/full/LuckyNumberFull.kt | 19 ++ .../edziennik/data/db/full/MessageFull.kt | 9 +- .../edziennik/data/db/full/NoticeFull.java | 16 -- .../edziennik/data/db/full/NoticeFull.kt | 22 +++ .../data/db/full/TeacherAbsenceFull.kt | 19 +- .../data/db/migration/Migration86.kt | 176 ++++++++++++++++++ .../data/firebase/SzkolnyAppFirebase.kt | 8 +- .../dialogs/bell/BellSyncTimeChooseDialog.kt | 2 +- .../edziennik/ui/dialogs/day/DayDialog.kt | 2 +- .../ui/dialogs/event/EventManualDialog.kt | 9 +- .../ui/dialogs/grade/GradeDetailsDialog.kt | 2 +- .../teacherabsence/TeacherAbsenceAdapter.kt | 2 +- .../teacherabsence/TeacherAbsenceDialog.kt | 2 +- .../ui/modules/agenda/AgendaFragment.kt | 4 +- .../announcements/AnnouncementsAdapter.java | 21 ++- .../announcements/AnnouncementsFragment.java | 10 +- .../modules/attendance/AttendanceAdapter.java | 17 +- .../attendance/AttendanceFragment.java | 24 +-- .../modules/behaviour/BehaviourFragment.java | 4 +- .../ui/modules/behaviour/NoticesAdapter.kt | 4 +- .../grades/editor/GradesEditorFragment.kt | 2 +- .../grades/viewholder/GradeViewHolder.kt | 2 +- .../ui/modules/home/CounterActivity.kt | 2 +- .../ui/modules/home/cards/HomeGradesCard.kt | 3 +- .../modules/home/cards/HomeLuckyNumberCard.kt | 2 +- .../modules/timetable/TimetableDayFragment.kt | 2 +- .../ui/modules/views/TimeDropdown.kt | 2 +- .../luckynumber/WidgetLuckyNumberProvider.kt | 2 +- .../utils/managers/AttendanceManager.kt | 50 +++++ .../main/res/layout/dialog_grade_details.xml | 2 +- 144 files changed, 1788 insertions(+), 1492 deletions(-) delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt index 7194aa04..051dff38 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -66,6 +66,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { val timetableManager by lazy { TimetableManager(this) } val eventManager by lazy { EventManager(this) } val permissionManager by lazy { PermissionManager(this) } + val attendanceManager by lazy { AttendanceManager(this) } val db get() = App.db diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt index 57dceb0e..92aecbd8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt @@ -68,6 +68,9 @@ object Regexes { } + val MOBIDZIENNIK_ATTENDANCE_TYPES by lazy { + """Legenda:.+?normal;">(.+?)""".toRegex(DOT_MATCHES_ALL) + } val MOBIDZIENNIK_ATTENDANCE_TABLE by lazy { """(.+?)
""".toRegex(DOT_MATCHES_ALL) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt index 11f048b5..32ca353c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt @@ -45,24 +45,25 @@ class EdudziennikWebAnnouncements(override val data: DataEdudziennik, val addedDate = Date.fromIsoHm(dateString) val announcementObject = Announcement( - profileId, - id, - subject, - null, - startDate, - null, - teacher.id, - longId - ) + profileId = profileId, + id = id, + subject = subject, + text = null, + startDate = startDate, + endDate = null, + teacherId = teacher.id, + addedDate = addedDate + ).also { + it.idString = longId + } - data.announcementIgnoreList.add(announcementObject) + data.announcementList.add(announcementObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_ANNOUNCEMENT, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt index 92545117..e6f75694 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt @@ -39,12 +39,12 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik, val attendanceTypes = EDUDZIENNIK_ATTENDANCE_TYPES.find(text)?.get(1)?.split(',')?.map { val type = EDUDZIENNIK_ATTENDANCE_TYPE.find(it.trim()) - val symbol = type?.get(1)?.trim() - val name = type?.get(2)?.trim() + val symbol = type?.get(1)?.trim() ?: "?" + val name = type?.get(2)?.trim() ?: "nieznany rodzaj" return@map Triple( symbol, name, - when (name?.toLowerCase(Locale.ROOT)) { + when (name.toLowerCase(Locale.ROOT)) { "obecność" -> Attendance.TYPE_PRESENT "nieobecność" -> Attendance.TYPE_ABSENT "spóźnienie" -> Attendance.TYPE_BELATED @@ -52,7 +52,7 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik, "dzień wolny" -> Attendance.TYPE_DAY_FREE "brak zajęć" -> Attendance.TYPE_DAY_FREE "oddelegowany" -> Attendance.TYPE_RELEASED - else -> Attendance.TYPE_CUSTOM + else -> Attendance.TYPE_UNKNOWN } ) } ?: emptyList() @@ -62,38 +62,42 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik, val lessonNumber = attendanceElement[2].toInt() val attendanceSymbol = attendanceElement[3] - val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) val lesson = lessons.firstOrNull { it.lessonNumber == lessonNumber } val id = "${date.stringY_m_d}:$lessonNumber:$attendanceSymbol".crc32() - val (_, name, type) = attendanceTypes.firstOrNull { (symbol, _, _) -> symbol == attendanceSymbol } + val (typeSymbol, typeName, baseType) = attendanceTypes.firstOrNull { (symbol, _, _) -> symbol == attendanceSymbol } ?: return@forEach val startTime = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }?.startTime ?: return@forEach val attendanceObject = Attendance( - profileId, - id, - lesson?.displayTeacherId ?: -1, - lesson?.displaySubjectId ?: -1, - profile.currentSemester, - name, - date, - lesson?.displayStartTime ?: startTime, - type - ) + profileId = profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = data.app.attendanceManager.getTypeShort(baseType), + typeSymbol = typeSymbol, + typeColor = null, + date = date, + startTime = lesson?.displayStartTime ?: startTime, + semester = profile.currentSemester, + teacherId = lesson?.displayTeacherId ?: -1, + subjectId = lesson?.displaySubjectId ?: -1 + ).also { + it.lessonNumber = lessonNumber + } data.attendanceList.add(attendanceObject) - if(type != Attendance.TYPE_PRESENT) { + if (baseType != Attendance.TYPE_PRESENT) { data.metadataList.add(Metadata( profileId, Metadata.TYPE_ATTENDANCE, id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt index 52833b9a..35c68478 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt @@ -57,8 +57,7 @@ class EdudziennikWebEvents(override val data: DataEdudziennik, Metadata.TYPE_EVENT, id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt index 8e7836fd..f6abf637 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt @@ -46,7 +46,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik, if (dateString.isBlank()) return@forEach val date = Date.fromY_m_d(dateString) - val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) val startTime = lessons.firstOrNull { it.displaySubjectId == subject.id }?.displayStartTime val eventTypeElement = examElement.child(3).child(0) @@ -74,8 +74,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik, Metadata.TYPE_EVENT, id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt index 50557333..459f13aa 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt @@ -126,7 +126,8 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, comment = null, semester = semester, teacherId = teacher.id, - subjectId = subject.id + subjectId = subject.id, + addedDate = addedDate ) data.gradeList.add(gradeObject) @@ -135,8 +136,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } @@ -168,8 +168,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, Metadata.TYPE_GRADE, proposedGradeObject.id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } @@ -201,8 +200,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, Metadata.TYPE_GRADE, finalGradeObject.id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt index acabcfa6..169a5127 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt @@ -43,7 +43,7 @@ class EdudziennikWebHomework(override val data: DataEdudziennik, val subjectName = subjectElement.text() val subject = data.getSubject(subjectId, subjectName) - val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime val teacherName = homeworkElement.child(2).text() @@ -72,8 +72,7 @@ class EdudziennikWebHomework(override val data: DataEdudziennik, Metadata.TYPE_HOMEWORK, id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt index 70c6849a..db8164c8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt @@ -24,9 +24,9 @@ class EdudziennikWebLuckyNumber(override val data: DataEdudziennik, webGet(TAG, data.schoolEndpoint + "Lucky", xhr = true) { text -> text.toIntOrNull()?.also { luckyNumber -> val luckyNumberObject = LuckyNumber( - profileId, - Date.getToday(), - luckyNumber + profileId = profileId, + date = Date.getToday(), + number = luckyNumber ) data.luckyNumberList.add(luckyNumberObject) @@ -35,8 +35,7 @@ class EdudziennikWebLuckyNumber(override val data: DataEdudziennik, Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - profile.empty, - System.currentTimeMillis() + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt index a111ef79..35a78168 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt @@ -41,12 +41,15 @@ class EdudziennikWebNotes(override val data: DataEdudziennik, val description = noteElement.child(3).text() val noticeObject = Notice( - profileId, - id, - description, - profile.currentSemester, - Notice.TYPE_NEUTRAL, - teacher.id + profileId = profileId, + id = id, + type = Notice.TYPE_NEUTRAL, + semester = profile.currentSemester, + text = description, + category = null, + points = null, + teacherId = teacher.id, + addedDate = addedDate ) data.noticeList.add(noticeObject) @@ -55,8 +58,7 @@ class EdudziennikWebNotes(override val data: DataEdudziennik, Metadata.TYPE_NOTICE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt index 3a31e208..94db15ef 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt @@ -124,8 +124,7 @@ class EdudziennikWebTimetable(override val data: DataEdudziennik, Metadata.TYPE_LESSON_CHANGE, lessonObject.id, seen, - seen, - System.currentTimeMillis() + seen )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt index 0e000fb1..d5e59596 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt @@ -68,9 +68,9 @@ class IdziennikApiCurrentRegister(override val data: DataIdziennik, val luckyNumberObject = LuckyNumber( - data.profileId, - luckyNumberDate, - luckyNumber + profileId = data.profileId, + date = luckyNumberDate, + number = luckyNumber ) data.luckyNumberList.add(luckyNumberObject) @@ -80,8 +80,7 @@ class IdziennikApiCurrentRegister(override val data: DataIdziennik, Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt index 58e7660c..622068e2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt @@ -69,7 +69,8 @@ class IdziennikApiMessagesInbox(override val data: DataIdziennik, type = if (jMessage.getBoolean("rekordUsuniety") == true) TYPE_DELETED else TYPE_RECEIVED, subject = subject, body = body, - senderId = rTeacher.id + senderId = rTeacher.id, + addedDate = sentDate ) val messageRecipient = MessageRecipient( @@ -87,8 +88,7 @@ class IdziennikApiMessagesInbox(override val data: DataIdziennik, Metadata.TYPE_MESSAGE, message.id, readDate > 0, - readDate > 0 || profile?.empty ?: false, - sentDate + readDate > 0 || profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt index 286c9896..87b3264d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt @@ -51,7 +51,8 @@ class IdziennikApiMessagesSent(override val data: DataIdziennik, type = TYPE_SENT, subject = subject, body = body, - senderId = null + senderId = null, + addedDate = sentDate ) for (recipientEl in jMessage.getAsJsonArray("odbiorcy")) { @@ -76,7 +77,7 @@ class IdziennikApiMessagesSent(override val data: DataIdziennik, } data.messageList.add(message) - data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate)) + data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true)) } data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt index f8744efa..531cbeeb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt @@ -52,14 +52,14 @@ class IdziennikWebAnnouncements(override val data: DataIdziennik, val startDate = jAnnouncement.getString("DataWydarzenia")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull()?.let { Date.fromMillis(it) } val announcementObject = Announcement( - profileId, - announcementId, - jAnnouncement.get("Temat").asString, - jAnnouncement.get("Tresc").asString, - startDate, - null, - rTeacher.id, - null + profileId = profileId, + id = announcementId, + subject = jAnnouncement.get("Temat").asString, + text = jAnnouncement.get("Tresc").asString, + startDate = startDate, + endDate = null, + teacherId = rTeacher.id, + addedDate = addedDate ) data.announcementList.add(announcementObject) data.metadataList.add(Metadata( @@ -67,8 +67,7 @@ class IdziennikWebAnnouncements(override val data: DataIdziennik, Metadata.TYPE_ANNOUNCEMENT, announcementObject.id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt index aa0a9998..d67acbdf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt @@ -12,10 +12,18 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNI import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.data.db.entity.Attendance.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_BELATED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT_CUSTOM +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_UNKNOWN import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getInt import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -51,71 +59,97 @@ class IdziennikWebAttendance(override val data: DataIdziennik, for (jAttendanceEl in json.getAsJsonArray("Obecnosci")) { val jAttendance = jAttendanceEl.asJsonObject // jAttendance - val attendanceTypeIdziennik = jAttendance.get("TypObecnosci").asInt - if (attendanceTypeIdziennik == 5 || attendanceTypeIdziennik == 7) - continue - val attendanceDate = Date.fromY_m_d(jAttendance.get("Data").asString) - val attendanceTime = Time.fromH_m(jAttendance.get("OdDoGodziny").asString) - if (attendanceDate.combineWith(attendanceTime) > System.currentTimeMillis()) + val type = jAttendance.get("TypObecnosci").asInt + + // skip "zajęcia nie odbyły się" and "Ferie" + if (type == 5 || type == 7) continue - val attendanceId = jAttendance.get("IdLesson").asString.crc16().toLong() + val date = Date.fromY_m_d(jAttendance.get("Data").asString) + val time = Time.fromH_m(jAttendance.get("OdDoGodziny").asString) + if (date.combineWith(time) > System.currentTimeMillis()) + continue + + val id = jAttendance.get("IdLesson").asString.crc16().toLong() val rSubject = data.getSubject(jAttendance.get("Przedmiot").asString, jAttendance.get("IdPrzedmiot").asLong, "") val rTeacher = data.getTeacherByFDotSpaceLast(jAttendance.get("PrzedmiotNauczyciel").asString) - var attendanceName = "obecność" - var attendanceType = Attendance.TYPE_CUSTOM + var baseType = TYPE_UNKNOWN + var typeName = "nieznany rodzaj" + var typeSymbol: String? = null + var typeColor: Long? = null - when (attendanceTypeIdziennik) { - 1 /* nieobecność usprawiedliwiona */ -> { - attendanceName = "nieobecność usprawiedliwiona" - attendanceType = TYPE_ABSENT_EXCUSED + /* https://iuczniowie.progman.pl/idziennik/mod_panelRodzica/obecnosci/obecnosciUcznia_lmt637231494660000000.js */ + /* https://iuczniowie.progman.pl/idziennik/mod_panelRodzica/obecnosci/obecnosci_lmt637231494660000000.css */ + when (type) { + 1 -> { + baseType = TYPE_ABSENT_EXCUSED + typeName = "nieobecność usprawiedliwiona" + typeColor = 0xffffe099 } - 2 /* spóźnienie */ -> { - attendanceName = "spóźnienie" - attendanceType = TYPE_BELATED + 2 -> { + baseType = TYPE_BELATED + typeName = "spóźnienie" + typeColor = 0xffffffaa } - 3 /* nieobecność nieusprawiedliwiona */ -> { - attendanceName = "nieobecność nieusprawiedliwiona" - attendanceType = TYPE_ABSENT + 3 -> { + baseType = TYPE_ABSENT + typeName = "nieobecność nieusprawiedliwiona" + typeColor = 0xffffad99 } - 4 /* zwolnienie */, 9 /* zwolniony / obecny */ -> { - attendanceType = TYPE_RELEASED - if (attendanceTypeIdziennik == 4) - attendanceName = "zwolnienie" - if (attendanceTypeIdziennik == 9) - attendanceName = "zwolnienie / obecność" + 4, 9 -> { + baseType = TYPE_RELEASED + if (type == 4) { + typeName = "zwolnienie" + typeColor = 0xffa8beff + } + if (type == 9) { + typeName = "zwolniony / obecny" + typeSymbol = "zb" + typeColor = 0xffff69b4 + } } - 0 /* obecny */, 8 /* Wycieczka */ -> { - attendanceType = TYPE_PRESENT - if (attendanceTypeIdziennik == 8) - attendanceName = "wycieczka" + 8 -> { + baseType = TYPE_PRESENT_CUSTOM + typeName = "wycieczka" + typeSymbol = "w" + typeColor = null + } + 0 -> { + baseType = TYPE_PRESENT + typeName = "obecny" + typeColor = 0xffccffcc } } - val semester = profile?.dateToSemester(attendanceDate) ?: 1 + val semester = profile?.dateToSemester(date) ?: 1 val attendanceObject = Attendance( - profileId, - attendanceId, - rTeacher.id, - rSubject.id, - semester, - attendanceName, - attendanceDate, - attendanceTime, - attendanceType - ) + profileId = profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = typeSymbol ?: data.app.attendanceManager.getTypeShort(baseType), + typeSymbol = typeSymbol ?: data.app.attendanceManager.getTypeShort(baseType), + typeColor = typeColor?.toInt(), + date = date, + startTime = time, + semester = semester, + teacherId = rTeacher.id, + subjectId = rSubject.id + ).also { + it.lessonTopic = jAttendance.getString("PrzedmiotTemat") + it.lessonNumber = jAttendance.getInt("Godzina") + } data.attendanceList.add(attendanceObject) - if (attendanceObject.type != TYPE_PRESENT) { + if (attendanceObject.baseType != TYPE_PRESENT) { data.metadataList.add(Metadata( profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt index 82e63e79..25c06945 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt @@ -68,7 +68,7 @@ class IdziennikWebExams(override val data: DataIdziennik, val teacherId = data.getTeacherByLastFirst(teacherName).id val topic = exam.getString("zakres")?.trim() ?: "" - val lessonList = data.db.timetableDao().getForDateNow(profileId, examDate) + val lessonList = data.db.timetableDao().getAllForDateNow(profileId, examDate) val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime val eventType = when (exam.getString("rodzaj")?.toLowerCase(Locale.getDefault())) { @@ -98,8 +98,7 @@ class IdziennikWebExams(override val data: DataIdziennik, Metadata.TYPE_EVENT, eventObject.id, profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt index e709122d..bd1808df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt @@ -94,8 +94,7 @@ class IdziennikWebGetMessage(override val data: DataIdziennik, Metadata.TYPE_MESSAGE, message.id, message.seen, - message.notified, - message.addedDate + message.notified )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt index b3237c82..90e17401 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt @@ -63,6 +63,8 @@ class IdziennikWebGrades(override val data: DataIdziennik, colorInt = Color.parseColor("#$gradeColor") } + val addedDate = grade.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() + val gradeObject = Grade( profileId = profileId, id = id, @@ -76,7 +78,9 @@ class IdziennikWebGrades(override val data: DataIdziennik, comment = null, semester = semester, teacherId = teacher.id, - subjectId = subject.id) + subjectId = subject.id, + addedDate = addedDate + ) when (grade.getInt("Typ")) { 0 -> { @@ -100,6 +104,8 @@ class IdziennikWebGrades(override val data: DataIdziennik, colorInt = Color.parseColor("#$historyColor") } + val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() + val historyObject = Grade( profileId = profileId, id = gradeObject.id * -1, @@ -113,19 +119,18 @@ class IdziennikWebGrades(override val data: DataIdziennik, comment = null, semester = historyItem.getInt("Semestr") ?: 1, teacherId = teacher.id, - subjectId = subject.id) + subjectId = subject.id, + addedDate = addedDate + ) historyObject.parentId = gradeObject.id - val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() - data.gradeList.add(historyObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_GRADE, historyObject.id, true, - true, - addedDate + true )) } // update the current grade's value with an average of all historical grades and itself @@ -147,8 +152,6 @@ class IdziennikWebGrades(override val data: DataIdziennik, } } - val addedDate = grade.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() - data.gradeList.add(gradeObject) data.metadataList.add( Metadata( @@ -156,8 +159,7 @@ class IdziennikWebGrades(override val data: DataIdziennik, Metadata.TYPE_GRADE, id, data.profile.empty, - data.profile.empty, - addedDate + data.profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt index 2289e13d..10b4a0c3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt @@ -57,7 +57,7 @@ class IdziennikWebHomework(override val data: DataIdziennik, val subjectId = data.getSubject(subjectName, null, subjectName).id val teacherName = homework.getString("usr") ?: return@forEach val teacherId = data.getTeacherByLastFirst(teacherName).id - val lessonList = data.db.timetableDao().getForDateNow(profileId, eventDate) + val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate) val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.displayStartTime val topic = homework.getString("tytul")?.trim() ?: "" @@ -77,7 +77,8 @@ class IdziennikWebHomework(override val data: DataIdziennik, type = Event.TYPE_HOMEWORK, teacherId = teacherId, subjectId = subjectId, - teamId = data.teamClass?.id ?: -1 + teamId = data.teamClass?.id ?: -1, + addedDate = addedDate.inMillis ) data.eventList.add(eventObject) @@ -86,8 +87,7 @@ class IdziennikWebHomework(override val data: DataIdziennik, Metadata.TYPE_HOMEWORK, eventObject.id, seen, - seen, - addedDate.inMillis + seen )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt index dcc560d5..64108bfd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt @@ -13,9 +13,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Notice -import pl.szczodrzynski.edziennik.data.db.entity.Notice.* +import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_NEGATIVE +import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_NEUTRAL +import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_POSITIVE import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.models.Date class IdziennikWebNotices(override val data: DataIdziennik, @@ -53,20 +56,24 @@ class IdziennikWebNotices(override val data: DataIdziennik, } val noticeObject = Notice( - profileId, - noticeId, - jNotice.get("Tresc").asString, - jNotice.get("Semestr").asInt, - nType, - rTeacher.id) + profileId = profileId, + id = noticeId, + type = nType, + semester = jNotice.get("Semestr").asInt, + text = jNotice.getString("Tresc") ?: "", + category = null, + points = null, + teacherId = rTeacher.id, + addedDate = addedDate.inMillis + ) + data.noticeList.add(noticeObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_NOTICE, noticeObject.id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate.inMillis + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt index c2639e51..b23e9f98 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt @@ -76,6 +76,11 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik, else -1 if (semester1Proposed != "") { + val addedDate = if (data.profile.empty) + data.profile.dateSemester1Start.inMillis + else + System.currentTimeMillis() + val gradeObject = Grade( profileId = profileId, id = semester1Id, @@ -89,26 +94,26 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik, comment = null, semester = 1, teacherId = -1, - subjectId = subjectObject.id + subjectId = subjectObject.id, + addedDate = addedDate ) - val addedDate = if (data.profile.empty) - data.profile.dateSemester1Start.inMillis - else - System.currentTimeMillis() - data.gradeList.add(gradeObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.empty, - profile.empty, - addedDate + profile.empty )) } if (semester2Proposed != "") { + val addedDate = if (data.profile.empty) + data.profile.dateSemester2Start.inMillis + else + System.currentTimeMillis() + val gradeObject = Grade( profileId = profileId, id = semester2Id, @@ -122,22 +127,17 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik, comment = null, semester = 2, teacherId = -1, - subjectId = subjectObject.id + subjectId = subjectObject.id, + addedDate = addedDate ) - val addedDate = if (data.profile.empty) - data.profile.dateSemester2Start.inMillis - else - System.currentTimeMillis() - data.gradeList.add(gradeObject) data.metadataList.add(Metadata( profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.empty, - profile.empty, - addedDate + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt index 973bc74f..343ebcac 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt @@ -59,7 +59,7 @@ class IdziennikWebSendMessage(override val data: DataIdziennik, IdziennikApiMessagesSent(data, null) { val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject } val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } - val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + val event = MessageSentEvent(data.profileId, message, message?.addedDate) EventBus.getDefault().postSticky(event) onSuccess() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt index 066346cd..e7d293c4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt @@ -165,8 +165,7 @@ class IdziennikWebTimetable(override val data: DataIdziennik, Metadata.TYPE_LESSON_CHANGE, lessonObject.id, seen, - seen, - System.currentTimeMillis() + seen )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt index e15f9410..2c6ad0e7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt @@ -74,9 +74,9 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { Regexes.IDZIENNIK_WEB_LUCKY_NUMBER.find(text)?.also { val number = it[1].toIntOrNull() ?: return@also val luckyNumberObject = LuckyNumber( - data.profileId, - Date.getToday(), - number + profileId = data.profileId, + date = Date.getToday(), + number = number ) data.luckyNumberList.add(luckyNumberObject) @@ -86,8 +86,7 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - profile.empty, - System.currentTimeMillis() + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt index d40c1a9a..23cfceb4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt @@ -37,8 +37,7 @@ class LibrusApiAnnouncementMarkAsRead(override val data: DataLibrus, Metadata.TYPE_ANNOUNCEMENT, announcement.id, announcement.seen, - announcement.notified, - announcement.addedDate + announcement.notified )) onSuccess() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt index 8256b859..d19be231 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt @@ -38,15 +38,17 @@ class LibrusApiAnnouncements(override val data: DataLibrus, val read = announcement.getBoolean("WasRead") ?: false val announcementObject = Announcement( - profileId, - id, - subject, - text, - startDate, - endDate, - teacherId, - longId - ) + profileId = profileId, + id = id, + subject = subject, + text = text, + startDate = startDate, + endDate = endDate, + teacherId = teacherId, + addedDate = addedDate + ).also { + it.idString = longId + } data.announcementList.add(announcementObject) data.setSeenMetadataList.add(Metadata( @@ -54,8 +56,7 @@ class LibrusApiAnnouncements(override val data: DataLibrus, Metadata.TYPE_ANNOUNCEMENT, id, read, - profile.empty || read, - addedDate + profile.empty || read )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt index d7cbc00c..c620d13d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt @@ -26,25 +26,39 @@ class LibrusApiAttendanceTypes(override val data: DataLibrus, attendanceTypes?.forEach { attendanceType -> val id = attendanceType.getLong("Id") ?: return@forEach - val name = attendanceType.getString("Name") ?: "" - val color = attendanceType.getString("ColorRGB")?.let { Color.parseColor("#$it") } ?: -1 - val standardId = when (attendanceType.getBoolean("Standard") ?: false) { - true -> id - false -> attendanceType.getJsonObject("StandardType")?.getLong("Id") ?: id - } - val type = when (standardId) { + val typeName = attendanceType.getString("Name") ?: "" + val typeSymbol = attendanceType.getString("Short") ?: "" + val typeColor = attendanceType.getString("ColorRGB")?.let { Color.parseColor("#$it") } + + val isStandard = attendanceType.getBoolean("Standard") ?: false + val baseType = when (attendanceType.getJsonObject("StandardType")?.getLong("Id") ?: id) { 1L -> Attendance.TYPE_ABSENT 2L -> Attendance.TYPE_BELATED 3L -> Attendance.TYPE_ABSENT_EXCUSED 4L -> Attendance.TYPE_RELEASED - /*100*/else -> Attendance.TYPE_PRESENT + /*100*/else -> when (isStandard) { + true -> Attendance.TYPE_PRESENT + false -> Attendance.TYPE_PRESENT_CUSTOM + } + } + val typeShort = when (isStandard) { + true -> data.app.attendanceManager.getTypeShort(baseType) + false -> typeSymbol } - data.attendanceTypes.put(id, AttendanceType(profileId, id, name, type, color)) + data.attendanceTypes.put(id, AttendanceType( + profileId, + id, + baseType, + typeName, + typeShort, + typeSymbol, + typeColor + )) } - data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES, 4*DAY) + data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES, 2*DAY) onSuccess(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt index 3727788d..cf9d7729 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt @@ -13,7 +13,6 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.utils.models.Date -import pl.szczodrzynski.edziennik.utils.models.Time class LibrusApiAttendances(override val data: DataLibrus, override val lastSync: Long?, @@ -42,9 +41,9 @@ class LibrusApiAttendances(override val data: DataLibrus, val lessonDate = Date.fromY_m_d(attendance.getString("Date")) val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id") val semester = attendance.getInt("Semester") ?: return@forEach - val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach - val typeObject = data.attendanceTypes[type] ?: null - val topic = typeObject?.name ?: "" + + val typeId = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach + val type = data.attendanceTypes[typeId] ?: null val startTime = data.lessonRanges.get(lessonNo)?.startTime @@ -52,29 +51,34 @@ class LibrusApiAttendances(override val data: DataLibrus, data.librusLessons.singleOrNull { it.lessonId == lessonId } else null - val attendanceObject = Attendance( - profileId, - id, - teacherId ?: lesson?.teacherId ?: -1, - lesson?.subjectId ?: -1, - semester, - topic, - lessonDate, - startTime ?: Time(0, 0, 0), - typeObject?.type ?: Attendance.TYPE_CUSTOM - ) - val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach) + val attendanceObject = Attendance( + profileId = profileId, + id = id, + baseType = type?.baseType ?: Attendance.TYPE_UNKNOWN, + typeName = type?.typeName ?: "nieznany rodzaj", + typeShort = type?.typeShort ?: "?", + typeSymbol = type?.typeSymbol ?: "?", + typeColor = type?.typeColor, + date = lessonDate, + startTime = startTime, + semester = semester, + teacherId = teacherId ?: lesson?.teacherId ?: -1, + subjectId = lesson?.subjectId ?: -1, + addedDate = addedDate + ).also { + it.lessonNumber = lessonNo + } + data.attendanceList.add(attendanceObject) - if(typeObject?.type != Attendance.TYPE_PRESENT) { + if(type?.baseType != Attendance.TYPE_PRESENT) { data.metadataList.add(Metadata( profileId, Metadata.TYPE_ATTENDANCE, id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate + profile?.empty ?: false )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt index 5a374ec9..fae79a50 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt @@ -55,7 +55,8 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, comment = null, semester = 1, teacherId = -1, - subjectId = 1 + subjectId = 1, + addedDate = profile.getSemesterStart(1).inMillis ) data.gradeList.add(semester1StartGradeObject) @@ -64,8 +65,7 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, semester1StartGradeObject.id, true, - true, - profile.getSemesterStart(1).inMillis + true )) } @@ -83,7 +83,8 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, comment = null, semester = 2, teacherId = -1, - subjectId = 1 + subjectId = 1, + addedDate = profile.getSemesterStart(2).inMillis ) data.gradeList.add(semester2StartGradeObject) @@ -92,8 +93,7 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, semester2StartGradeObject.id, true, - true, - profile.getSemesterStart(2).inMillis + true )) } @@ -155,7 +155,8 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, comment = if (text != null) description.join(" - ") else null, semester = semester, teacherId = teacherId, - subjectId = 1 + subjectId = 1, + addedDate = addedDate ).apply { valueMax = valueTo } @@ -166,8 +167,7 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt index 42e318e0..8cc4f1c1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt @@ -65,7 +65,8 @@ class LibrusApiDescriptiveGrades(override val data: DataLibrus, comment = null, semester = semester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ) data.gradeList.add(gradeObject) @@ -74,8 +75,7 @@ class LibrusApiDescriptiveGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt index 52f52844..5999a48e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt @@ -56,7 +56,8 @@ class LibrusApiEvents(override val data: DataLibrus, type = type, teacherId = teacherId, subjectId = subjectId, - teamId = teamId + teamId = teamId, + addedDate = addedDate ) data.eventList.add(eventObject) @@ -66,8 +67,7 @@ class LibrusApiEvents(override val data: DataLibrus, Metadata.TYPE_EVENT, id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt index 7ff42fde..c2e2d0ce 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt @@ -79,7 +79,8 @@ class LibrusApiGrades(override val data: DataLibrus, comment = null, semester = semester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ) grade.getJsonObject("Improvement")?.also { @@ -98,8 +99,7 @@ class LibrusApiGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt index d47fbfdf..c1183c19 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt @@ -43,7 +43,8 @@ class LibrusApiHomework(override val data: DataLibrus, type = -1, teacherId = teacherId, subjectId = -1, - teamId = -1 + teamId = -1, + addedDate = addedDate.inMillis ) data.eventList.add(eventObject) @@ -52,8 +53,7 @@ class LibrusApiHomework(override val data: DataLibrus, Metadata.TYPE_HOMEWORK, id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate.inMillis + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt index 5a52cd69..80e6e299 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt @@ -33,9 +33,9 @@ class LibrusApiLuckyNumber(override val data: DataLibrus, val luckyNumberDate = Date.fromY_m_d(luckyNumberEl.getString("LuckyNumberDay")) ?: Date.getToday() val luckyNumber = luckyNumberEl.getInt("LuckyNumber") ?: -1 val luckyNumberObject = LuckyNumber( - profileId, - luckyNumberDate, - luckyNumber + profileId = profileId, + date = luckyNumberDate, + number = luckyNumber ) if (luckyNumberDate >= Date.getToday()) @@ -50,8 +50,7 @@ class LibrusApiLuckyNumber(override val data: DataLibrus, Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt index e05cbfd7..ba531afe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt @@ -46,12 +46,15 @@ class LibrusApiNotices(override val data: DataLibrus, val semester = profile?.dateToSemester(addedDate) ?: 1 val noticeObject = Notice( - profileId, - id, - categoryText + "\n" + text, - semester, - type, - teacherId + profileId = profileId, + id = id, + type = type, + semester = semester, + text = text, + category = categoryText, + points = null, + teacherId = teacherId, + addedDate = addedDate.inMillis ) data.noticeList.add(noticeObject) @@ -61,8 +64,7 @@ class LibrusApiNotices(override val data: DataLibrus, Metadata.TYPE_NOTICE, id, profile?.empty ?: false, - profile?.empty ?: false, - addedDate.inMillis + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt index fae45318..9da2c435 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt @@ -56,7 +56,8 @@ class LibrusApiPointGrades(override val data: DataLibrus, comment = null, semester = semester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ).apply { valueMax = category?.valueTo ?: 0f } @@ -67,8 +68,7 @@ class LibrusApiPointGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt index de696a99..6c0a9dd7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt @@ -58,8 +58,7 @@ class LibrusApiPtMeetings(override val data: DataLibrus, Metadata.TYPE_EVENT, id, profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt index 3d4db1aa..973ada46 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt @@ -43,15 +43,15 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus, val timeTo = teacherAbsence.getString("TimeTo")?.let { Time.fromH_m_s(it) } val teacherAbsenceObject = TeacherAbsence( - profileId, - id, - teacherId, - type, - name, - dateFrom, - dateTo, - timeFrom, - timeTo + profileId = profileId, + id = id, + type = type, + name = name, + dateFrom = dateFrom, + dateTo = dateTo, + timeFrom = timeFrom, + timeTo = timeTo, + teacherId = teacherId ) data.teacherAbsenceList.add(teacherAbsenceObject) @@ -60,8 +60,7 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus, Metadata.TYPE_TEACHER_ABSENCE, id, true, - profile?.empty ?: false, - System.currentTimeMillis() + profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt index cf020425..7b7c8c6f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt @@ -60,7 +60,8 @@ class LibrusApiTextGrades(override val data: DataLibrus, comment = grade.getString("Phrase") /* whatever it is */, semester = semester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ) data.gradeList.add(gradeObject) @@ -69,8 +70,7 @@ class LibrusApiTextGrades(override val data: DataLibrus, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt index 7c1eec61..9a463a6b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt @@ -198,8 +198,7 @@ class LibrusApiTimetables(override val data: DataLibrus, Metadata.TYPE_LESSON_CHANGE, lessonObject.id, seen, - seen, - System.currentTimeMillis() + seen )) } data.lessonList.add(lessonObject) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt index 35148cef..9f962bf9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt @@ -97,7 +97,8 @@ class LibrusMessagesGetList(override val data: DataLibrus, type = type, subject = subject, body = null, - senderId = senderId + senderId = senderId, + addedDate = sentDate ) val messageRecipientObject = MessageRecipient( @@ -120,8 +121,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, Metadata.TYPE_MESSAGE, id, notified, - notified, - sentDate + notified )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt index bb4c620b..49ee0d52 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt @@ -150,8 +150,7 @@ class LibrusMessagesGetMessage(override val data: DataLibrus, Metadata.TYPE_MESSAGE, messageObject.id, true, - true, - messageObject.addedDate + true )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt index 9364f2b8..d568135e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt @@ -50,7 +50,7 @@ class LibrusMessagesSendMessage(override val data: DataLibrus, LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = null) { val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.id == id } val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } - val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + val event = MessageSentEvent(data.profileId, message, message?.addedDate) EventBus.getDefault().postSticky(event) onSuccess() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt index 48f88ef4..d33f6911 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt @@ -58,7 +58,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus, elements[9].select("input").attr("onclick") )?.get(1)?.toLong() ?: return@forEachIndexed - val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate) + val lessons = data.db.timetableDao().getAllForDateNow(profileId, eventDate) val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime val seen = when (profile.empty) { @@ -76,7 +76,8 @@ class LibrusSynergiaHomework(override val data: DataLibrus, type = Event.TYPE_HOMEWORK, teacherId = teacherId, subjectId = subjectId, - teamId = data.teamClass?.id ?: -1 + teamId = data.teamClass?.id ?: -1, + addedDate = addedDate.inMillis ) data.eventList.add(eventObject) @@ -85,8 +86,7 @@ class LibrusSynergiaHomework(override val data: DataLibrus, Metadata.TYPE_HOMEWORK, id, seen, - seen, - addedDate.inMillis + seen )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt index be68de4d..d9d7abb7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt @@ -6,7 +6,10 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.data.db.entity.Attendance.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED import pl.szczodrzynski.edziennik.data.db.entity.Metadata class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) { @@ -23,7 +26,7 @@ class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) val id = cols[0].toLong() val lessonId = cols[1].toLong() data.mobiLessons.singleOrNull { it.id == lessonId }?.let { lesson -> - val type = when (cols[4]) { + val baseType = when (cols[4]) { "2" -> TYPE_ABSENT "5" -> TYPE_ABSENT_EXCUSED "4" -> TYPE_RELEASED @@ -31,16 +34,37 @@ class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) } val semester = data.profile?.dateToSemester(lesson.date) ?: 1 + val typeName = when (baseType) { + TYPE_ABSENT -> "nieobecność" + TYPE_ABSENT_EXCUSED -> "nieobecność usprawiedliwiona" + TYPE_RELEASED -> "zwolnienie" + TYPE_PRESENT -> "obecność" + else -> "nieznany rodzaj" + } + val typeSymbol = when (baseType) { + TYPE_ABSENT -> "|" + TYPE_ABSENT_EXCUSED -> "+" + TYPE_RELEASED -> "z" + TYPE_PRESENT -> "." + else -> "?" + } + val attendanceObject = Attendance( - data.profileId, - id, - lesson.teacherId, - lesson.subjectId, - semester, - lesson.topic, - lesson.date, - lesson.startTime, - type) + profileId = data.profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = data.app.attendanceManager.getTypeShort(baseType), + typeSymbol = typeSymbol, + typeColor = null, + date = lesson.date, + startTime = lesson.startTime, + semester = semester, + teacherId = lesson.teacherId, + subjectId = lesson.subjectId + ).also { + it.lessonTopic = lesson.topic + } data.attendanceList.add(attendanceObject) data.metadataList.add( @@ -49,8 +73,7 @@ class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) Metadata.TYPE_ATTENDANCE, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt index 02fbb67e..7a72d3de 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt @@ -60,7 +60,9 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { type = type, teacherId = teacherId, subjectId = subjectId, - teamId = teamId) + teamId = teamId, + addedDate = addedDate + ) data.eventList.add(eventObject) data.metadataList.add( @@ -69,8 +71,7 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { Metadata.TYPE_EVENT, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + data.profile?.empty ?: false )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt index 609c895c..c3c1b449 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt @@ -79,7 +79,9 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List) { comment = null, semester = semester, teacherId = teacherId, - subjectId = subjectId) + subjectId = subjectId, + addedDate = addedDate + ) if (data.profile?.empty == true) { addedDate = data.profile.dateSemester1Start.inMillis @@ -92,8 +94,7 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List) { Metadata.TYPE_GRADE, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + data.profile?.empty ?: false )) addedDate++ } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt index 4623a26d..683a8820 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt @@ -40,7 +40,8 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List) { type = Event.TYPE_HOMEWORK, teacherId = teacherId, subjectId = subjectId, - teamId = teamId) + teamId = teamId + ) data.eventList.add(eventObject) data.metadataList.add( @@ -49,8 +50,7 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List) { Metadata.TYPE_HOMEWORK, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt index f1d9aed2..66bfe1fd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt @@ -33,12 +33,16 @@ class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { val addedDate = Date.fromYmd(cols[7]).inMillis val noticeObject = Notice( - data.profileId, - id, - text, - semester, - type, - teacherId) + profileId = data.profileId, + id = id, + type = type, + semester = semester, + text = text, + category = null, + points = null, + teacherId = teacherId, + addedDate = addedDate + ) data.noticeList.add(noticeObject) data.metadataList.add( @@ -47,8 +51,7 @@ class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { Metadata.TYPE_NOTICE, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - addedDate + data.profile?.empty ?: false )) } }} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt index 6a7ce02d..a82ab4ef 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt @@ -8,9 +8,9 @@ import android.util.SparseArray import androidx.core.util.set import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.LessonRange import pl.szczodrzynski.edziennik.data.db.entity.Metadata -import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.fixName import pl.szczodrzynski.edziennik.keys import pl.szczodrzynski.edziennik.singleOrNull @@ -97,8 +97,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) { Metadata.TYPE_LESSON_CHANGE, it.id, seen, - seen, - System.currentTimeMillis() + seen )) } data.lessonList += it diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikLuckyNumberExtractor.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikLuckyNumberExtractor.kt index 5e1445ec..ece23d7f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikLuckyNumberExtractor.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikLuckyNumberExtractor.kt @@ -17,9 +17,9 @@ class MobidziennikLuckyNumberExtractor(val data: DataMobidziennik, text: String) val luckyNumber = it.groupValues[1].toInt() val luckyNumberObject = LuckyNumber( - data.profileId, - Date.getToday(), - luckyNumber + profileId = data.profileId, + date = Date.getToday(), + number = luckyNumber ) data.luckyNumberList.add(luckyNumberObject) @@ -29,8 +29,7 @@ class MobidziennikLuckyNumberExtractor(val data: DataMobidziennik, text: String) Metadata.TYPE_LUCKY_NUMBER, luckyNumberObject.date.value.toLong(), true, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } catch (_: Exception){} } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt index c56c56d9..57fef0a8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt @@ -11,7 +11,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBID import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.data.db.entity.Attendance.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_BELATED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_UNKNOWN import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.fixName @@ -71,6 +76,18 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, val start = System.currentTimeMillis() + val types = Regexes.MOBIDZIENNIK_ATTENDANCE_TYPES + .find(text) + ?.get(1) + ?.split("
") + ?.map { + it.trimEnd(',') + .split(" ", limit = 2) + .let { it.getOrNull(0) to it.getOrNull(1) } + } + ?.toMap() + val typeSymbols = types?.keys?.filterNotNull() ?: listOf() + Regexes.MOBIDZIENNIK_ATTENDANCE_TABLE.findAll(text).forEach { tableResult -> val table = tableResult[1] val lessonDates = mutableListOf() @@ -92,14 +109,14 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, return@forEach ranges.forEach { range -> val lessonDate = dateIterator.next() - val entry = entriesIterator.next() + var entry = entriesIterator.next() if (entry.isBlank()) return@forEach val startTime = Time.fromH_m(range[1]) - val entryIterator = entry.iterator() + range[2].split(" / ").mapNotNull { Regexes.MOBIDZIENNIK_ATTENDANCE_LESSON.find(it) }.forEachIndexed { index, lesson -> val topic = lesson[2] - if (topic.startsWith("Lekcja odwołana: ") || !entryIterator.hasNext()) + if (topic.startsWith("Lekcja odwołana: ") || entry.isEmpty()) return@forEachIndexed val subjectName = lesson[1] //val team = lesson[3] @@ -108,39 +125,58 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == teacherName }?.id ?: -1 val subjectId = data.subjectList.singleOrNull { it.longName == subjectName }?.id ?: -1 - val type = when (entryIterator.nextChar()) { - '.' -> TYPE_PRESENT - '|' -> TYPE_ABSENT - '+' -> TYPE_ABSENT_EXCUSED - 's' -> TYPE_BELATED - 'z' -> TYPE_RELEASED - else -> TYPE_PRESENT + var typeSymbol = "" + for (symbol in typeSymbols) { + if (entry.startsWith(symbol) && symbol.length > typeSymbol.length) + typeSymbol = symbol } + entry = entry.removePrefix(typeSymbol) + + val baseType = when (typeSymbol) { + "." -> TYPE_PRESENT + "|" -> TYPE_ABSENT + "+" -> TYPE_ABSENT_EXCUSED + "s" -> TYPE_BELATED + "z" -> TYPE_RELEASED + else -> TYPE_UNKNOWN + } + val typeName = types?.get(typeSymbol) ?: "" + + val typeShort = when (baseType) { + TYPE_UNKNOWN -> typeSymbol + else -> data.app.attendanceManager.getTypeShort(baseType) + } + val semester = data.profile?.dateToSemester(lessonDate) ?: 1 val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson[0].hashCode() and 0xFFFF) + index val attendanceObject = Attendance( - data.profileId, - id, - teacherId, - subjectId, - semester, - topic, - lessonDate, - startTime, - type) + profileId = profileId, + id = id, + baseType = baseType, + typeName = typeName, + typeShort = typeShort, + typeSymbol = typeSymbol, + typeColor = null, + date = lessonDate, + startTime = startTime, + semester = semester, + teacherId = teacherId, + subjectId = subjectId + ).also { + it.lessonTopic = topic + } data.attendanceList.add(attendanceObject) - if (type != TYPE_PRESENT) { + if (baseType != TYPE_PRESENT) { data.metadataList.add( Metadata( data.profileId, Metadata.TYPE_ATTENDANCE, id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt index c1749d76..95c1f6df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt @@ -89,8 +89,8 @@ class MobidziennikWebCalendar(override val data: DataMobidziennik, Metadata.TYPE_EVENT, eventObject.id, profile?.empty ?: false, - profile?.empty ?: false, - System.currentTimeMillis() /* no addedDate here though */ + profile?.empty ?: false + /* no addedDate here though */ )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGetMessage.kt index 1c44a219..105c0943 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGetMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGetMessage.kt @@ -139,8 +139,7 @@ class MobidziennikWebGetMessage(override val data: DataMobidziennik, Metadata.TYPE_MESSAGE, message.id, true, - true, - message.addedDate + true )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGrades.kt index de058c91..9f5fab95 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebGrades.kt @@ -125,7 +125,8 @@ class MobidziennikWebGrades(override val data: DataMobidziennik, comment = null, semester = gradeSemester, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = gradeAddedDateMillis ) gradeObject.classAverage = gradeClassAverage @@ -137,8 +138,7 @@ class MobidziennikWebGrades(override val data: DataMobidziennik, Metadata.TYPE_GRADE, gradeObject.id, profile.empty, - profile.empty, - gradeAddedDateMillis + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt index c0373c54..4e8f900a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt @@ -77,11 +77,12 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik, type = type, subject = subject, body = null, - senderId = senderId + senderId = senderId, + addedDate = addedDate ) data.messageList.add(message) - data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, addedDate)) + data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true)) } // sync every 7 days as we probably don't expect more than diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesInbox.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesInbox.kt index 385ea71e..be762228 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesInbox.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesInbox.kt @@ -63,7 +63,8 @@ class MobidziennikWebMessagesInbox(override val data: DataMobidziennik, type = Message.TYPE_RECEIVED, subject = subject, body = null, - senderId = senderId + senderId = senderId, + addedDate = addedDate ) if (hasAttachments) @@ -76,8 +77,7 @@ class MobidziennikWebMessagesInbox(override val data: DataMobidziennik, Metadata.TYPE_MESSAGE, message.id, isRead, - isRead || profile?.empty ?: false, - addedDate + isRead || profile?.empty ?: false )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesSent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesSent.kt index 918816c8..543df329 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesSent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesSent.kt @@ -78,7 +78,8 @@ class MobidziennikWebMessagesSent(override val data: DataMobidziennik, type = Message.TYPE_SENT, subject = subject, body = null, - senderId = null + senderId = null, + addedDate = addedDate ) if (hasAttachments) @@ -91,8 +92,7 @@ class MobidziennikWebMessagesSent(override val data: DataMobidziennik, Metadata.TYPE_MESSAGE, message.id, true, - true, - addedDate + true )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebSendMessage.kt index d0cee4dc..89178b8b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebSendMessage.kt @@ -45,7 +45,7 @@ class MobidziennikWebSendMessage(override val data: DataMobidziennik, MobidziennikWebMessagesAll(data, null) { val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject } val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } - val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + val event = MessageSentEvent(data.profileId, message, message?.addedDate) EventBus.getDefault().postSticky(event) onSuccess() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt index 2ec240a4..2fc816ee 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt @@ -7,7 +7,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_ATTENDANCE import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.utils.models.Date @@ -38,38 +38,43 @@ class VulcanApiAttendance(override val data: DataVulcan, json.getJsonObject("Data")?.getJsonArray("Frekwencje")?.forEach { attendanceEl -> val attendance = attendanceEl.asJsonObject - val attendanceCategory = data.attendanceTypes.get(attendance.getLong("IdKategoria") ?: return@forEach) + val type = data.attendanceTypes.get(attendance.getLong("IdKategoria") ?: return@forEach) ?: return@forEach - val type = attendanceCategory.type - val id = (attendance.getInt("Dzien") ?: 0) + (attendance.getInt("Numer") ?: 0) val lessonDateMillis = Date.fromY_m_d(attendance.getString("DzienTekst")).inMillis val lessonDate = Date.fromMillis(lessonDateMillis) + val startTime = data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime val lessonSemester = profile.dateToSemester(lessonDate) val attendanceObject = Attendance( - profileId, - id.toLong(), - -1, - attendance.getLong("IdPrzedmiot") ?: -1, - lessonSemester, - attendance.getString("PrzedmiotNazwa") + attendanceCategory.name.let { " - $it" }, - lessonDate, - data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime, - type) + profileId = profileId, + id = id.toLong(), + baseType = type.baseType, + typeName = type.typeName, + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = lessonDate, + startTime = startTime, + semester = lessonSemester, + teacherId = -1, + subjectId = attendance.getLong("IdPrzedmiot") ?: -1, + addedDate = lessonDate.combineWith(startTime) + ).also { + it.lessonNumber = attendance.getInt("Numer") + } data.attendanceList.add(attendanceObject) - if (attendanceObject.type != TYPE_PRESENT) { + if (type.baseType != TYPE_PRESENT) { data.metadataList.add(Metadata( profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, profile.empty, - profile.empty, - attendanceObject.lessonDate.combineWith(attendanceObject.startTime) + profile.empty )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiDictionaries.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiDictionaries.kt index 76f53cf5..2dc9f6bc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiDictionaries.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiDictionaries.kt @@ -114,11 +114,11 @@ class VulcanApiDictionaries(override val data: DataVulcan, private fun saveAttendanceType(attendanceType: JsonObject) { val id = attendanceType.getLong("Id") ?: return - val name = attendanceType.getString("Nazwa") ?: "" + val typeName = attendanceType.getString("Nazwa") ?: "" val absent = attendanceType.getBoolean("Nieobecnosc") ?: false val excused = attendanceType.getBoolean("Usprawiedliwione") ?: false - val type = if (absent) { + val baseType = if (absent) { if (excused) Attendance.TYPE_ABSENT_EXCUSED else @@ -137,15 +137,35 @@ class VulcanApiDictionaries(override val data: DataVulcan, else if (present) Attendance.TYPE_PRESENT else - Attendance.TYPE_CUSTOM + Attendance.TYPE_UNKNOWN + } + + val (typeColor, typeSymbol) = when (id.toInt()) { + 1 -> 0xffffffff to "●" // obecność + 2 -> 0xffffa687 to "—" // nieobecność + 3 -> 0xfffcc150 to "u" // nieobecność usprawiedliwiona + 4 -> 0xffede049 to "s" // spóźnienie + 5 -> 0xffbbdd5f to "su" // spóźnienie usprawiedliwione + 6 -> 0xffa9c9fd to "ns" // nieobecny z przyczyn szkolnych + 7 -> 0xffddbbe5 to "z" // zwolniony + 8 -> 0xffffffff to "" // usunięty wpis + else -> null to "?" + } + + val typeShort = when (id.toInt()) { + 6 -> "ns" // nieobecny z przyczyn szkolnych + 8 -> "" // usunięty wpis + else -> data.app.attendanceManager.getTypeShort(baseType) } val attendanceTypeObject = AttendanceType( profileId, id, - name, - type, - -1 + baseType, + typeName, + typeShort, + typeSymbol, + typeColor?.toInt() ) data.attendanceTypes.put(id, attendanceTypeObject) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt index 4ba7c460..4366807a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt @@ -59,7 +59,7 @@ class VulcanApiEvents(override val data: DataVulcan, val teacherId = event.getLong("IdPracownik") ?: -1 val topic = event.getString("Opis")?.trim() ?: "" - val lessonList = data.db.timetableDao().getForDateNow(profileId, eventDate) + val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate) val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime val type = when (isHomework) { @@ -90,8 +90,7 @@ class VulcanApiEvents(override val data: DataVulcan, if (isHomework) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, id, profile.empty, - profile.empty, - System.currentTimeMillis() + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt index f62778fc..815bd83b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt @@ -101,7 +101,8 @@ class VulcanApiGrades(override val data: DataVulcan, comment = null, semester = data.studentSemesterNumber, teacherId = teacherId, - subjectId = subjectId + subjectId = subjectId, + addedDate = addedDate ) data.gradeList.add(gradeObject) @@ -110,9 +111,7 @@ class VulcanApiGrades(override val data: DataVulcan, Metadata.TYPE_GRADE, id, profile.empty, - profile.empty, - addedDate - + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesChangeStatus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesChangeStatus.kt index 5e226849..b0b7795d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesChangeStatus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesChangeStatus.kt @@ -37,8 +37,7 @@ class VulcanApiMessagesChangeStatus(override val data: DataVulcan, Metadata.TYPE_MESSAGE, messageObject.id, true, - true, - messageObject.addedDate + true )) messageObject.seen = true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesInbox.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesInbox.kt index 388a97ad..dea913c5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesInbox.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesInbox.kt @@ -72,7 +72,8 @@ class VulcanApiMessagesInbox(override val data: DataVulcan, type = TYPE_RECEIVED, subject = subject, body = body.replace("\n", "
"), - senderId = senderId + senderId = senderId, + addedDate = sentDate ) val messageRecipientObject = MessageRecipient( @@ -90,8 +91,7 @@ class VulcanApiMessagesInbox(override val data: DataVulcan, Metadata.TYPE_MESSAGE, id, readDate > 0, - readDate > 0, - sentDate + readDate > 0 )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesSent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesSent.kt index d190c00f..94cfdcba 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesSent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiMessagesSent.kt @@ -97,7 +97,8 @@ class VulcanApiMessagesSent(override val data: DataVulcan, type = TYPE_SENT, subject = subject, body = body.replace("\n", "
"), - senderId = null + senderId = null, + addedDate = sentDate ) data.messageList.add(messageObject) @@ -106,8 +107,7 @@ class VulcanApiMessagesSent(override val data: DataVulcan, Metadata.TYPE_MESSAGE, id, true, - true, - sentDate + true )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt index b2202981..56b4d918 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt @@ -16,7 +16,6 @@ import pl.szczodrzynski.edziennik.getJsonArray import pl.szczodrzynski.edziennik.getLong import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.toSparseArray -import pl.szczodrzynski.edziennik.utils.models.Date class VulcanApiNotices(override val data: DataVulcan, override val lastSync: Long?, @@ -41,15 +40,21 @@ class VulcanApiNotices(override val data: DataVulcan, val id = notice.getLong("Id") ?: return@forEach val text = notice.getString("TrescUwagi") ?: return@forEach val teacherId = notice.getLong("IdPracownik") ?: -1 - val addedDate = Date.fromY_m_d(notice.getString("DataWpisuTekst")).inMillis + val addedDate = notice.getLong("DataModyfikacji")?.times(1000) ?: System.currentTimeMillis() + + val categoryId = notice.getLong("IdKategoriaUwag") ?: -1 + val categoryText = data.noticeTypes[categoryId]?.name ?: "" val noticeObject = Notice( - profileId, - id, - text, - profile.currentSemester, - Notice.TYPE_NEUTRAL, - teacherId + profileId = profileId, + id = id, + type = Notice.TYPE_NEUTRAL, + semester = profile.currentSemester, + text = text, + category = categoryText, + points = null, + teacherId = teacherId, + addedDate = addedDate ) data.noticeList.add(noticeObject) @@ -58,8 +63,7 @@ class VulcanApiNotices(override val data: DataVulcan, Metadata.TYPE_NOTICE, id, profile.empty, - profile.empty, - addedDate + profile.empty )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt index ee1e250f..771ee5c8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt @@ -82,8 +82,7 @@ class VulcanApiProposedGrades(override val data: DataVulcan, Metadata.TYPE_GRADE, gradeObject.id, data.profile?.empty ?: false, - data.profile?.empty ?: false, - System.currentTimeMillis() + data.profile?.empty ?: false )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiSendMessage.kt index b88ccdb6..8c89b620 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiSendMessage.kt @@ -54,7 +54,7 @@ class VulcanApiSendMessage(override val data: DataVulcan, VulcanApiMessagesSent(data, null) { val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject } val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == messageId } - val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + val event = MessageSentEvent(data.profileId, message, message?.addedDate) EventBus.getDefault().postSticky(event) onSuccess() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt index d8899dbf..74687a7b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt @@ -184,8 +184,7 @@ class VulcanApiTimetable(override val data: DataVulcan, Metadata.TYPE_LESSON_CHANGE, lessonObject.id, seen, - seen, - System.currentTimeMillis() + seen )) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt index 2f0c88af..b6c31d84 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt @@ -88,6 +88,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt var teacherOnConflictStrategy = OnConflictStrategy.IGNORE var eventListReplace = false var messageListReplace = false + var announcementListReplace = false val classrooms = LongSparseArray() val attendanceTypes = LongSparseArray() @@ -120,7 +121,6 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt val attendanceList = mutableListOf() val announcementList = mutableListOf() - val announcementIgnoreList = mutableListOf() val luckyNumberList = mutableListOf() @@ -178,7 +178,6 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt noticeList.clear() attendanceList.clear() announcementList.clear() - announcementIgnoreList.clear() luckyNumberList.clear() teacherAbsenceList.clear() messageList.clear() @@ -284,39 +283,19 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt d("Metadata saved in ${System.currentTimeMillis()-startTime} ms") startTime = System.currentTimeMillis() - if (lessonList.isNotEmpty()) { - db.timetableDao() += lessonList - } - if (gradeList.isNotEmpty()) { - db.gradeDao().addAll(gradeList) - } - if (eventList.isNotEmpty()) { - if (eventListReplace) - db.eventDao().replaceAll(eventList) - else - db.eventDao().upsertAll(eventList, removeNotKept = true) - } + db.timetableDao().putAll(lessonList, removeNotKept = true) + db.gradeDao().putAll(gradeList, removeNotKept = true) + db.eventDao().putAll(eventList, forceReplace = eventListReplace, removeNotKept = true) if (noticeList.isNotEmpty()) { db.noticeDao().clear(profile.id) - db.noticeDao().addAll(noticeList) + db.noticeDao().putAll(noticeList) } - if (attendanceList.isNotEmpty()) - db.attendanceDao().addAll(attendanceList) - if (announcementList.isNotEmpty()) - db.announcementDao().addAll(announcementList) - if (announcementIgnoreList.isNotEmpty()) - db.announcementDao().addAllIgnore(announcementIgnoreList) - if (luckyNumberList.isNotEmpty()) - db.luckyNumberDao().addAll(luckyNumberList) - if (teacherAbsenceList.isNotEmpty()) - db.teacherAbsenceDao().addAll(teacherAbsenceList) + db.attendanceDao().putAll(attendanceList, removeNotKept = true) + db.announcementDao().putAll(announcementList, forceReplace = announcementListReplace, removeNotKept = false) + db.luckyNumberDao().putAll(luckyNumberList) + db.teacherAbsenceDao().putAll(teacherAbsenceList) - if (messageList.isNotEmpty()) { - if (messageListReplace) - db.messageDao().replaceAll(messageList) - else - db.messageDao().upsertAll(messageList, removeNotKept = false) // TODO dataRemoveModel for messages - } + db.messageDao().putAll(messageList, forceReplace = messageListReplace, removeNotKept = false) if (messageRecipientList.isNotEmpty()) db.messageRecipientDao().addAll(messageRecipientList) if (messageRecipientIgnoreList.isNotEmpty()) @@ -357,7 +336,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt } fun shouldSyncLuckyNumber(): Boolean { - return (db.luckyNumberDao().getNearestFutureNow(profileId, Date.getToday().value) ?: -1) == -1 + return db.luckyNumberDao().getNearestFutureNow(profileId, Date.getToday()) == null } /*fun error(tag: String, errorCode: Int, response: Response? = null, throwable: Throwable? = null, apiResponse: JsonObject? = null) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt index f247ee19..fabf5f39 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt @@ -20,10 +20,10 @@ open class DataRemoveModel { fun commit(profileId: Int, dao: TimetableDao) { if (dateFrom != null && dateTo != null) { - dao.clearBetweenDates(profileId, dateFrom, dateTo) + dao.dontKeepBetweenDates(profileId, dateFrom, dateTo) } else { - dateFrom?.let { dateFrom -> dao.clearFromDate(profileId, dateFrom) } - dateTo?.let { dateTo -> dao.clearToDate(profileId, dateTo) } + dateFrom?.let { dateFrom -> dao.dontKeepFromDate(profileId, dateFrom) } + dateTo?.let { dateTo -> dao.dontKeepToDate(profileId, dateTo) } } } } @@ -69,7 +69,7 @@ open class DataRemoveModel { fun commit(profileId: Int, dao: AttendanceDao) { if (dateFrom != null) { - dao.clearAfterDate(profileId, dateFrom) + dao.dontKeepAfterDate(profileId, dateFrom) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt index 63d4ec88..c10bab3e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt @@ -40,8 +40,7 @@ class AppSync(val app: App, val notifications: MutableList, val pr Metadata.TYPE_EVENT, event.id, isPast || markAsSeen || event.seen, - isPast || markAsSeen || event.notified, - event.addedDate + isPast || markAsSeen || event.notified ) }) return app.db.eventDao().upsertAll(events).size diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt index 39b68499..21a5c112 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt @@ -53,7 +53,7 @@ class Notifications(val app: App, val notifications: MutableList, profileId = lesson.profileId, profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name, viewId = MainActivity.DRAWER_ITEM_TIMETABLE, - addedDate = lesson.addedDate + addedDate = System.currentTimeMillis() ).addExtra("timetableDate", lesson.displayDate?.stringY_m_d ?: "") } } @@ -117,7 +117,7 @@ class Notifications(val app: App, val notifications: MutableList, } private fun gradeNotifications() { - for (grade in app.db.gradeDao().notNotifiedNow) { + for (grade in app.db.gradeDao().getNotNotifiedNow()) { val gradeName = when (grade.type) { Grade.TYPE_SEMESTER1_PROPOSED, Grade.TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format_2, grade.name) Grade.TYPE_SEMESTER1_FINAL, Grade.TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format_2, grade.name) @@ -144,7 +144,7 @@ class Notifications(val app: App, val notifications: MutableList, } private fun behaviourNotifications() { - for (notice in app.db.noticeDao().notNotifiedNow) { + for (notice in app.db.noticeDao().getNotNotifiedNow()) { val noticeTypeStr = when (notice.type) { Notice.TYPE_POSITIVE -> app.getString(R.string.notification_notice_praise) @@ -155,7 +155,7 @@ class Notifications(val app: App, val notifications: MutableList, val text = app.getString( R.string.notification_notice_format, noticeTypeStr, - notice.teacherFullName, + notice.teacherName, Date.fromMillis(notice.addedDate).formattedString ) notifications += Notification( @@ -172,9 +172,9 @@ class Notifications(val app: App, val notifications: MutableList, } private fun attendanceNotifications() { - for (attendance in app.db.attendanceDao().notNotifiedNow) { + for (attendance in app.db.attendanceDao().getNotNotifiedNow()) { - val attendanceTypeStr = when (attendance.type) { + val attendanceTypeStr = when (attendance.baseType) { Attendance.TYPE_ABSENT -> app.getString(R.string.notification_absence) Attendance.TYPE_ABSENT_EXCUSED -> app.getString(R.string.notification_absence_excused) Attendance.TYPE_BELATED -> app.getString(R.string.notification_belated) @@ -191,7 +191,7 @@ class Notifications(val app: App, val notifications: MutableList, R.string.notification_attendance_format, attendanceTypeStr, attendance.subjectLongName, - attendance.lessonDate.formattedString + attendance.date.formattedString ) notifications += Notification( id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id), @@ -207,10 +207,10 @@ class Notifications(val app: App, val notifications: MutableList, } private fun announcementNotifications() { - for (announcement in app.db.announcementDao().notNotifiedNow) { + for (announcement in app.db.announcementDao().getNotNotifiedNow()) { val text = app.getString( R.string.notification_announcement_format, - announcement.teacherFullName, + announcement.teacherName, announcement.subject ) notifications += Notification( @@ -247,9 +247,9 @@ class Notifications(val app: App, val notifications: MutableList, } private fun luckyNumberNotifications() { - val luckyNumbers = app.db.luckyNumberDao().notNotifiedNow - luckyNumbers?.removeAll { it.date < today } - luckyNumbers?.forEach { luckyNumber -> + val luckyNumbers = app.db.luckyNumberDao().getNotNotifiedNow().toMutableList() + luckyNumbers.removeAll { it.date < today } + luckyNumbers.forEach { luckyNumber -> val profile = profiles.singleOrNull { it.id == luckyNumber.profileId } ?: return@forEach val text = when (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) { true -> when (luckyNumber.date.value) { @@ -271,7 +271,7 @@ class Notifications(val app: App, val notifications: MutableList, profileId = luckyNumber.profileId, profileName = profile.name, viewId = MainActivity.DRAWER_ITEM_HOME, - addedDate = luckyNumber.addedDate + addedDate = System.currentTimeMillis() ) } } @@ -280,7 +280,7 @@ class Notifications(val app: App, val notifications: MutableList, for (teacherAbsence in app.db.teacherAbsenceDao().getNotNotifiedNow()) { val message = app.getString( R.string.notification_teacher_absence_new_format, - teacherAbsence.teacherFullName + teacherAbsence.teacherName ) notifications += Notification( id = Notification.buildId(teacherAbsence.profileId, Notification.TYPE_TEACHER_ABSENCE, teacherAbsence.id), diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt index b57cc4d4..0cd2fe26 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt @@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.* LibrusLesson::class, TimetableManual::class, Metadata::class -], version = 85) +], version = 86) @TypeConverters( ConverterTime::class, ConverterDate::class, @@ -170,7 +170,8 @@ abstract class AppDb : RoomDatabase() { Migration82(), Migration83(), Migration84(), - Migration85() + Migration85(), + Migration86() ).allowMainThreadQueries().build() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.java deleted file mode 100644 index ab32fb7c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.dao; - -import androidx.lifecycle.LiveData; -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; -import androidx.room.RawQuery; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.util.List; - -import pl.szczodrzynski.edziennik.data.db.entity.Announcement; -import pl.szczodrzynski.edziennik.data.db.entity.Metadata; -import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull; - -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ANNOUNCEMENT; - -@Dao -public abstract class AnnouncementDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract long add(Announcement announcement); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void addAll(List announcementList); - - @Insert(onConflict = OnConflictStrategy.IGNORE) - public abstract void addAllIgnore(List announcementList); - - @Query("DELETE FROM announcements WHERE profileId = :profileId") - public abstract void clear(int profileId); - - @RawQuery(observedEntities = {Announcement.class, Metadata.class}) - abstract LiveData> getAll(SupportSQLiteQuery query); - public LiveData> getAll(int profileId, String filter) { - return getAll(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM announcements \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON announcementId = thingId AND thingType = "+TYPE_ANNOUNCEMENT+" AND metadata.profileId = "+profileId+"\n" + - "WHERE announcements.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public LiveData> getAll(int profileId) { - return getAll(profileId, "1"); - } - public LiveData> getAllWhere(int profileId, String filter) { - return getAll(profileId, filter); - } - - @RawQuery(observedEntities = {Announcement.class, Metadata.class}) - abstract List getAllNow(SupportSQLiteQuery query); - public List getAllNow(int profileId, String filter) { - return getAllNow(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM announcements \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON announcementId = thingId AND thingType = "+TYPE_ANNOUNCEMENT+" AND metadata.profileId = "+profileId+"\n" + - "WHERE announcements.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public List getNotNotifiedNow(int profileId) { - return getAllNow(profileId, "notified = 0"); - } - - @Query("SELECT " + - "*, " + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName " + - "FROM announcements " + - "LEFT JOIN teachers USING(profileId, teacherId) " + - "LEFT JOIN metadata ON announcementId = thingId AND thingType = "+TYPE_ANNOUNCEMENT+" AND metadata.profileId = announcements.profileId " + - "WHERE notified = 0 " + - "ORDER BY addedDate DESC") - public abstract List getNotNotifiedNow(); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.kt new file mode 100644 index 00000000..67e034c5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AnnouncementDao.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.data.db.entity.Announcement +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull + +@Dao +@SelectiveDao(db = AppDb::class) +abstract class AnnouncementDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName + FROM announcements + LEFT JOIN teachers USING(profileId, teacherId) + LEFT JOIN metadata ON announcementId = thingId AND thingType = ${Metadata.TYPE_ANNOUNCEMENT} AND metadata.profileId = announcements.profileId + """ + + private const val ORDER_BY = """ORDER BY addedDate DESC""" + } + + private val selective by lazy { AnnouncementDaoSelective(App.db) } + + @RawQuery(observedEntities = [Announcement::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Announcement::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "announcementId"], skippedColumns = ["addedDate", "announcementText"]) + override fun update(item: Announcement) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + + // CLEAR + @Query("DELETE FROM announcements WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM announcements WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE announcements.profileId = $profileId $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE announcements.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE announcements.profileId = $profileId AND notified = 0 $ORDER_BY") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE announcements.profileId = $profileId AND announcementId = $id") +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.java deleted file mode 100644 index 95c3147f..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.dao; - -import androidx.lifecycle.LiveData; -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; -import androidx.room.RawQuery; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.util.List; - -import pl.szczodrzynski.edziennik.data.db.entity.Attendance; -import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull; -import pl.szczodrzynski.edziennik.utils.models.Date; - -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ATTENDANCE; - -@Dao -public abstract class AttendanceDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract long add(Attendance attendance); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void addAll(List attendanceList); - - @Query("DELETE FROM attendances WHERE profileId = :profileId") - public abstract void clear(int profileId); - - @Query("DELETE FROM attendances WHERE profileId = :profileId AND attendanceLessonDate > :date") - public abstract void clearAfterDate(int profileId, Date date); - - @RawQuery(observedEntities = {Attendance.class}) - abstract LiveData> getAll(SupportSQLiteQuery query); - public LiveData> getAll(int profileId, String filter) { - return getAll(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM attendances \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN subjects USING(profileId, subjectId)\n" + - "LEFT JOIN metadata ON attendanceId = thingId AND thingType = " + TYPE_ATTENDANCE + " AND metadata.profileId = "+profileId+"\n" + - "WHERE attendances.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY attendanceLessonDate DESC, attendanceStartTime DESC")); - } - public LiveData> getAll(int profileId) { - return getAll(profileId, "1"); - } - public LiveData> getAllWhere(int profileId, String filter) { - return getAll(profileId, filter); - } - - @RawQuery - abstract List getAllNow(SupportSQLiteQuery query); - public List getAllNow(int profileId, String filter) { - return getAllNow(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM attendances \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN subjects USING(profileId, subjectId)\n" + - "LEFT JOIN metadata ON attendanceId = thingId AND thingType = " + TYPE_ATTENDANCE + " AND metadata.profileId = "+profileId+"\n" + - "WHERE attendances.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY attendanceLessonDate DESC, attendanceStartTime DESC")); - } - public List getNotNotifiedNow(int profileId) { - return getAllNow(profileId, "notified = 0"); - } - - @Query("SELECT * FROM attendances " + - "LEFT JOIN subjects USING(profileId, subjectId) " + - "LEFT JOIN metadata ON attendanceId = thingId AND thingType = " + TYPE_ATTENDANCE + " AND metadata.profileId = attendances.profileId " + - "WHERE notified = 0 " + - "ORDER BY attendanceLessonDate DESC, attendanceStartTime DESC") - public abstract List getNotNotifiedNow(); - - // only absent and absent_excused count as absences - // all the other types are counted as being present - @Query("SELECT \n" + - "CAST(SUM(CASE WHEN attendanceType != "+Attendance.TYPE_ABSENT+" AND attendanceType != "+ Attendance.TYPE_ABSENT_EXCUSED+" THEN 1 ELSE 0 END) AS float)\n" + - " / \n" + - "CAST(count() AS float)*100 \n" + - "FROM attendances \n" + - "WHERE profileId = :profileId") - public abstract LiveData getAttendancePercentage(int profileId); - - @Query("SELECT \n" + - "CAST(SUM(CASE WHEN attendanceType != "+Attendance.TYPE_ABSENT+" AND attendanceType != "+Attendance.TYPE_ABSENT_EXCUSED+" THEN 1 ELSE 0 END) AS float)\n" + - " / \n" + - "CAST(count() AS float)*100 \n" + - "FROM attendances \n" + - "WHERE profileId = :profileId AND attendanceSemester = :semester") - public abstract float getAttendancePercentageNow(int profileId, int semester); - - @Query("SELECT \n" + - "CAST(SUM(CASE WHEN attendanceType != "+Attendance.TYPE_ABSENT+" AND attendanceType != "+Attendance.TYPE_ABSENT_EXCUSED+" THEN 1 ELSE 0 END) AS float)\n" + - " / \n" + - "CAST(count() AS float)*100 \n" + - "FROM attendances \n" + - "WHERE profileId = :profileId") - public abstract float getAttendancePercentageNow(int profileId); -} - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt new file mode 100644 index 00000000..de04c919 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.utils.models.Date + +@Dao +@SelectiveDao(db = AppDb::class) +abstract class AttendanceDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName + FROM attendances + LEFT JOIN teachers USING(profileId, teacherId) + LEFT JOIN subjects USING(profileId, subjectId) + LEFT JOIN metadata ON attendanceId = thingId AND thingType = ${Metadata.TYPE_ATTENDANCE} AND metadata.profileId = attendances.profileId + """ + + private const val ORDER_BY = """ORDER BY attendanceDate DESC, attendanceTime DESC""" + } + + private val selective by lazy { AttendanceDaoSelective(App.db) } + + @RawQuery(observedEntities = [Attendance::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Attendance::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "attendanceId"], skippedColumns = ["addedDate", "announcementText"]) + override fun update(item: Attendance) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + + // CLEAR + @Query("DELETE FROM attendances WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM attendances WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE attendances.profileId = $profileId $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE attendances.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE attendances.profileId = $profileId AND notified = 0 $ORDER_BY") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE attendances.profileId = $profileId AND attendanceId = $id") + + @Query("UPDATE attendances SET keep = 0 WHERE profileId = :profileId AND attendanceDate > :date") + abstract fun dontKeepAfterDate(profileId: Int, date: Date?) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt index 8c13dca9..94677d70 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt @@ -16,13 +16,15 @@ interface BaseDao { fun getRaw(query: SupportSQLiteQuery): LiveData> fun getRaw(query: String) = getRaw(SimpleSQLiteQuery(query)) @RawQuery + fun getOne(query: SupportSQLiteQuery): LiveData + fun getOne(query: String) = getOne(SimpleSQLiteQuery(query)) + @RawQuery fun getRawNow(query: SupportSQLiteQuery): List fun getRawNow(query: String) = getRawNow(SimpleSQLiteQuery(query)) @RawQuery fun getOneNow(query: SupportSQLiteQuery): F? fun getOneNow(query: String) = getOneNow(SimpleSQLiteQuery(query)) - @Query("DELETE FROM events WHERE keep = 0") fun removeNotKept() /** @@ -41,12 +43,14 @@ interface BaseDao { /** * REPLACE an [item] in the database, * removing any conflicting rows. + * Creates the item if it does not exist yet. */ @Insert(onConflict = OnConflictStrategy.REPLACE) fun replace(item: T) /** * REPLACE [items] in the database, * removing any conflicting rows. + * Creates items if it does not exist yet. */ @Insert(onConflict = OnConflictStrategy.REPLACE) fun replaceAll(items: List) @@ -97,4 +101,21 @@ interface BaseDao { if (removeNotKept) removeNotKept() return insertResult } + + /** + * Make sure that [items] are in the database. + * When [forceReplace] == false, do a selective update (UPSERT). + * When [forceReplace] == true, add all items replacing any conflicting ones (REPLACE). + * + * @param forceReplace whether to replace all items instead of selectively updating + * @param removeNotKept whether to remove all items whose [keep] parameter is false + */ + fun putAll(items: List, forceReplace: Boolean = false, removeNotKept: Boolean = false) { + if (items.isEmpty()) + return + if (forceReplace) + replaceAll(items) + else + upsertAll(items) + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt index b2017cc5..44b2962d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt @@ -31,8 +31,8 @@ abstract class EventDao : BaseDao { eventTypes.eventTypeName AS typeName, eventTypes.eventTypeColor AS typeColor FROM events - LEFT JOIN subjects USING(profileId, subjectId) LEFT JOIN teachers USING(profileId, teacherId) + LEFT JOIN subjects USING(profileId, subjectId) LEFT JOIN teams USING(profileId, teamId) LEFT JOIN eventTypes USING(profileId, eventType) LEFT JOIN metadata ON eventId = thingId AND (thingType = ${Metadata.TYPE_EVENT} OR thingType = ${Metadata.TYPE_HOMEWORK}) AND metadata.profileId = events.profileId @@ -47,6 +47,8 @@ abstract class EventDao : BaseDao { @RawQuery(observedEntities = [Event::class]) abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Event::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData // SELECTIVE UPDATE @UpdateSelective(primaryKeys = ["profileId", "eventId"], skippedColumns = ["eventIsDone", "eventBlacklisted", "homeworkBody", "attachmentIds", "attachmentNames"]) @@ -56,6 +58,9 @@ abstract class EventDao : BaseDao { // CLEAR @Query("DELETE FROM events WHERE profileId = :profileId") abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM events WHERE keep = 0") + abstract override fun removeNotKept() // GET ALL - LIVE DATA fun getAll(profileId: Int) = diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/GradeDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/GradeDao.kt index 9c3e93c5..5862859e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/GradeDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/GradeDao.kt @@ -1,32 +1,86 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-24. */ package pl.szczodrzynski.edziennik.data.db.dao import androidx.lifecycle.LiveData -import androidx.room.* -import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.room.Transaction import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.full.GradeFull +import pl.szczodrzynski.edziennik.utils.models.Date import java.util.* -import kotlin.collections.List import kotlin.collections.component1 import kotlin.collections.component2 -import kotlin.collections.iterator import kotlin.collections.set @Dao -abstract class GradeDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun add(grade: Grade): Long +@SelectiveDao(db = AppDb::class) +abstract class GradeDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName + FROM grades + LEFT JOIN teachers USING(profileId, teacherId) + LEFT JOIN subjects USING(profileId, subjectId) + LEFT JOIN metadata ON gradeId = thingId AND thingType = ${Metadata.TYPE_GRADE} AND metadata.profileId = grades.profileId + """ - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun addAll(gradeList: List) + private const val ORDER_BY = """ORDER BY addedDate DESC""" + } + private val selective by lazy { GradeDaoSelective(App.db) } + + @RawQuery(observedEntities = [Grade::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Grade::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "gradeId"], skippedColumns = ["addedDate", "gradeClassAverage"]) + override fun update(item: Grade) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + + // CLEAR @Query("DELETE FROM grades WHERE profileId = :profileId") - abstract fun clear(profileId: Int) + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM grades WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE grades.profileId = $profileId $ORDER_BY") + fun getAllFromDate(profileId: Int, date: Date) = + getRaw("$QUERY WHERE grades.profileId = $profileId AND addedDate > ${date.inMillis} $ORDER_BY") + fun getAllBySubject(profileId: Int, subjectId: Long) = + getRaw("$QUERY WHERE grades.profileId = $profileId AND subjectId = $subjectId $ORDER_BY") + fun getAllOrderBy(profileId: Int, orderBy: String) = + getRaw("$QUERY WHERE grades.profileId = $profileId ORDER BY $orderBy") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE grades.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE grades.profileId = $profileId AND notified = 0 $ORDER_BY") + fun getByParentIdNow(profileId: Int, parentId: Long) = + getRawNow("$QUERY WHERE grades.profileId = $profileId AND gradeParentId = $parentId $ORDER_BY") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE grades.profileId = $profileId AND gradeId = $id") @Query("DELETE FROM grades WHERE profileId = :profileId AND gradeType = :type") abstract fun clearWithType(profileId: Int, type: Int) @@ -37,66 +91,13 @@ abstract class GradeDao { @Query("DELETE FROM grades WHERE profileId = :profileId AND gradeSemester = :semester AND gradeType = :type") abstract fun clearForSemesterWithType(profileId: Int, semester: Int, type: Int) - @RawQuery(observedEntities = [Grade::class]) - abstract fun getAll(query: SupportSQLiteQuery?): LiveData> - fun getAll(profileId: Int, filter: String, orderBy: String): LiveData> { - return getAll(SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM grades \n" + - "LEFT JOIN subjects USING(profileId, subjectId)\n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON gradeId = thingId AND thingType = " + Metadata.TYPE_GRADE + " AND metadata.profileId = " + profileId + "\n" + - "WHERE grades.profileId = " + profileId + " AND " + filter + "\n" + - "ORDER BY " + orderBy)) // TODO: 2019-04-30 why did I add sorting by gradeType??? - } - - fun getAllOrderBy(profileId: Int, orderBy: String): LiveData> { - return getAll(profileId, "1", orderBy) - } - - fun getAllWhere(profileId: Int, filter: String): LiveData> { - return getAll(profileId, filter, "addedDate DESC") - } - - @RawQuery - abstract fun getAllNow(query: SupportSQLiteQuery?): List - - fun getAllNow(profileId: Int, filter: String): List { - return getAllNow(SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM grades \n" + - "LEFT JOIN subjects USING(profileId, subjectId)\n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON gradeId = thingId AND thingType = " + Metadata.TYPE_GRADE + " AND metadata.profileId = " + profileId + "\n" + - "WHERE grades.profileId = " + profileId + " AND " + filter + "\n" + - "ORDER BY addedDate DESC")) - } - - fun getNotNotifiedNow(profileId: Int): List { - return getAllNow(profileId, "notified = 0") - } - - fun getAllWithParentIdNow(profileId: Int, parentId: Long): List { - return getAllNow(profileId, "gradeParentId = $parentId") - } - - @get:Query("SELECT * FROM grades " + - "LEFT JOIN subjects USING(profileId, subjectId) " + - "LEFT JOIN metadata ON gradeId = thingId AND thingType = " + Metadata.TYPE_GRADE + " AND metadata.profileId = grades.profileId " + - "WHERE notified = 0 " + - "ORDER BY addedDate DESC") - abstract val notNotifiedNow: List - - @RawQuery - abstract fun getNow(query: SupportSQLiteQuery): GradeFull? + // GRADE DETAILS - MOBIDZIENNIK @Query("UPDATE grades SET gradeClassAverage = :classAverage, gradeColor = :color WHERE profileId = :profileId AND gradeId = :gradeId") abstract fun updateDetailsById(profileId: Int, gradeId: Long, classAverage: Float, color: Int) - @Query("UPDATE metadata SET addedDate = :addedDate WHERE profileId = :profileId AND thingType = " + Metadata.TYPE_GRADE + " AND thingId = :gradeId") + @Query("UPDATE grades SET addedDate = :addedDate WHERE profileId = :profileId AND gradeId = :gradeId") abstract fun updateAddedDateById(profileId: Int, gradeId: Long, addedDate: Long) @Transaction @@ -118,7 +119,7 @@ abstract class GradeDao { @Query("SELECT gradeColor FROM grades WHERE profileId = :profileId ORDER BY gradeId") abstract fun getColors(profileId: Int): List - @Query("SELECT addedDate FROM metadata WHERE profileId = :profileId AND thingType = " + Metadata.TYPE_GRADE + " ORDER BY thingId") + @Query("SELECT addedDate FROM grades WHERE profileId = :profileId ORDER BY gradeId") abstract fun getAddedDates(profileId: Int): List @Transaction @@ -134,8 +135,4 @@ abstract class GradeDao { gradeAddedDates[gradeId] = addedDates.next() } } - - fun getAllFromDate(profileId: Int, date: Long): LiveData> { - return getAllWhere(profileId, "addedDate > $date") - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.java deleted file mode 100644 index c1c33c23..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.dao; - -import androidx.annotation.Nullable; -import androidx.lifecycle.LiveData; -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; -import androidx.room.RawQuery; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.util.List; - -import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber; -import pl.szczodrzynski.edziennik.data.db.entity.Metadata; -import pl.szczodrzynski.edziennik.data.db.entity.Notice; -import pl.szczodrzynski.edziennik.data.db.full.LuckyNumberFull; -import pl.szczodrzynski.edziennik.utils.models.Date; - -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_LUCKY_NUMBER; - -@Dao -public abstract class LuckyNumberDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void add(LuckyNumber luckyNumber); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void addAll(List luckyNumberList); - - @Query("DELETE FROM luckyNumbers WHERE profileId = :profileId") - public abstract void clear(int profileId); - - @Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate = :date") - public abstract LiveData getByDate(int profileId, Date date); - - @Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate = :date") - public abstract LuckyNumber getByDateNow(int profileId, Date date); - - @Nullable - @Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate >= :date ORDER BY luckyNumberDate DESC LIMIT 1") - public abstract LuckyNumber getNearestFutureNow(int profileId, int date); - - @Query("SELECT * FROM luckyNumbers WHERE profileId = :profileId AND luckyNumberDate >= :date ORDER BY luckyNumberDate DESC LIMIT 1") - public abstract LiveData getNearestFuture(int profileId, int date); - - @RawQuery(observedEntities = {LuckyNumber.class}) - abstract LiveData> getAll(SupportSQLiteQuery query); - public LiveData> getAll(int profileId, String filter) { - return getAll(new SimpleSQLiteQuery("SELECT\n" + - "*\n" + - "FROM luckyNumbers\n" + - "LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = "+TYPE_LUCKY_NUMBER+" AND metadata.profileId = "+profileId+"\n" + - "WHERE luckyNumbers.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public LiveData> getAll(int profileId) { - return getAll(profileId, "1"); - } - public LiveData> getAllWhere(int profileId, String filter) { - return getAll(profileId, filter); - } - - @RawQuery(observedEntities = {Notice.class, Metadata.class}) - abstract List getAllNow(SupportSQLiteQuery query); - public List getAllNow(int profileId, String filter) { - return getAllNow(new SimpleSQLiteQuery("SELECT\n" + - "*\n" + - "FROM luckyNumbers\n" + - "LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = "+TYPE_LUCKY_NUMBER+" AND metadata.profileId = "+profileId+"\n" + - "WHERE luckyNumbers.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public List getNotNotifiedNow(int profileId) { - return getAllNow(profileId, "notified = 0"); - } - - @Query("SELECT * FROM luckyNumbers\n" + - "LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = "+TYPE_LUCKY_NUMBER+" AND metadata.profileId = luckyNumbers.profileId " + - "WHERE notified = 0 " + - "ORDER BY addedDate DESC") - public abstract List getNotNotifiedNow(); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.kt new file mode 100644 index 00000000..ad44c0d3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/LuckyNumberDao.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.full.LuckyNumberFull +import pl.szczodrzynski.edziennik.utils.models.Date + +@Dao +@SelectiveDao(db = AppDb::class) +abstract class LuckyNumberDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + * + FROM luckyNumbers + LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = ${Metadata.TYPE_LUCKY_NUMBER} AND metadata.profileId = luckyNumbers.profileId + """ + + private const val ORDER_BY = """ORDER BY luckyNumberDate DESC""" + } + + private val selective by lazy { LuckyNumberDaoSelective(App.db) } + + @RawQuery(observedEntities = [LuckyNumber::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [LuckyNumber::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "luckyNumberDate"], skippedColumns = ["addedDate"]) + override fun update(item: LuckyNumber) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + + // CLEAR + @Query("DELETE FROM luckyNumbers WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM luckyNumbers WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE luckyNumbers.profileId = $profileId $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE luckyNumbers.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE luckyNumbers.profileId = $profileId AND notified = 0 $ORDER_BY") + + // GET ONE - LIVE DATA + fun getNearestFuture(profileId: Int, today: Date) = + getOne("$QUERY WHERE luckyNumbers.profileId = $profileId AND luckyNumberDate >= ${today.value} $ORDER_BY LIMIT 1") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE attendances.profileId = $profileId AND attendanceId = $id") + fun getNearestFutureNow(profileId: Int, today: Date) = + getOneNow("$QUERY WHERE luckyNumbers.profileId = $profileId AND luckyNumberDate >= ${today.value} $ORDER_BY LIMIT 1") +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MessageDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MessageDao.kt index 606f29a7..a0be36ab 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MessageDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MessageDao.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ package pl.szczodrzynski.edziennik.data.db.dao @@ -36,7 +36,10 @@ abstract class MessageDao : BaseDao { @RawQuery(observedEntities = [Message::class]) abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Message::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + // SELECTIVE UPDATE @UpdateSelective(primaryKeys = ["profileId", "messageId"], skippedColumns = ["messageType", "messageBody", "messageIsPinned", "attachmentIds", "attachmentNames", "attachmentSizes"]) override fun update(item: Message) = selective.update(item) override fun updateAll(items: List) = selective.updateAll(items) @@ -44,6 +47,9 @@ abstract class MessageDao : BaseDao { // CLEAR @Query("DELETE FROM messages WHERE profileId = :profileId") abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM messages WHERE keep = 0") + abstract override fun removeNotKept() // GET ALL - LIVE DATA fun getAll(profileId: Int) = @@ -64,4 +70,6 @@ abstract class MessageDao : BaseDao { // GET ONE - NOW fun getByIdNow(profileId: Int, id: Long) = getOneNow("$QUERY WHERE messages.profileId = $profileId AND messageId = $id") + + } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java index 7a074bca..eac92f21 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java @@ -63,37 +63,37 @@ public abstract class MetadataDao { @Transaction public void setSeen(int profileId, Object o, boolean seen) { if (o instanceof Grade) { - if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), seen, false, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), seen, false)) == -1) { updateSeen(profileId, TYPE_GRADE, ((Grade) o).getId(), seen); } } if (o instanceof Attendance) { - if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).id, seen, false, 0)) == -1) { - updateSeen(profileId, TYPE_ATTENDANCE, ((Attendance) o).id, seen); + if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), seen, false)) == -1) { + updateSeen(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), seen); } } if (o instanceof Notice) { - if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).id, seen, false, 0)) == -1) { - updateSeen(profileId, TYPE_NOTICE, ((Notice) o).id, seen); + if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).getId(), seen, false)) == -1) { + updateSeen(profileId, TYPE_NOTICE, ((Notice) o).getId(), seen); } } if (o instanceof Event) { - if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen, false, 0)) == -1) { + if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen, false)) == -1) { updateSeen(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen); } } if (o instanceof LessonFull) { - if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), seen, false, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), seen, false)) == -1) { updateSeen(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), seen); } } if (o instanceof Announcement) { - if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, seen, false, 0)) == -1) { - updateSeen(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, seen); + if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), seen, false)) == -1) { + updateSeen(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), seen); } } if (o instanceof Message) { - if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), seen, false, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), seen, false)) == -1) { updateSeen(profileId, TYPE_MESSAGE, ((Message) o).getId(), seen); } } @@ -102,37 +102,37 @@ public abstract class MetadataDao { @Transaction public void setNotified(int profileId, Object o, boolean notified) { if (o instanceof Grade) { - if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), false, notified, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), false, notified)) == -1) { updateNotified(profileId, TYPE_GRADE, ((Grade) o).getId(), notified); } } if (o instanceof Attendance) { - if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).id, false, notified, 0)) == -1) { - updateNotified(profileId, TYPE_ATTENDANCE, ((Attendance) o).id, notified); + if (add(new Metadata(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), false, notified)) == -1) { + updateNotified(profileId, TYPE_ATTENDANCE, ((Attendance) o).getId(), notified); } } if (o instanceof Notice) { - if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).id, false, notified, 0)) == -1) { - updateNotified(profileId, TYPE_NOTICE, ((Notice) o).id, notified); + if (add(new Metadata(profileId, TYPE_NOTICE, ((Notice) o).getId(), false, notified)) == -1) { + updateNotified(profileId, TYPE_NOTICE, ((Notice) o).getId(), notified); } } if (o instanceof Event) { - if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), false, notified, 0)) == -1) { + if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), false, notified)) == -1) { updateNotified(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), notified); } } if (o instanceof LessonFull) { - if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), false, notified, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), false, notified)) == -1) { updateNotified(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).getId(), notified); } } if (o instanceof Announcement) { - if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, false, notified, 0)) == -1) { - updateNotified(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, notified); + if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), false, notified)) == -1) { + updateNotified(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).getId(), notified); } } if (o instanceof Message) { - if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), false, notified, 0)) == -1) { + if (add(new Metadata(profileId, TYPE_MESSAGE, ((Message) o).getId(), false, notified)) == -1) { updateNotified(profileId, TYPE_MESSAGE, ((Message) o).getId(), notified); } } @@ -141,7 +141,7 @@ public abstract class MetadataDao { @Transaction public void setBoth(int profileId, Event o, boolean seen, boolean notified, long addedDate) { if (o != null) { - if (add(new Metadata(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen, notified, addedDate)) == -1) { + if (add(new Metadata(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen, notified)) == -1) { updateSeen(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen); updateNotified(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), notified); } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.java deleted file mode 100644 index 110544e4..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.dao; - -import androidx.lifecycle.LiveData; -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; -import androidx.room.RawQuery; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.util.List; - -import pl.szczodrzynski.edziennik.data.db.entity.Metadata; -import pl.szczodrzynski.edziennik.data.db.entity.Notice; -import pl.szczodrzynski.edziennik.data.db.full.NoticeFull; - -import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_NOTICE; - -@Dao -public abstract class NoticeDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract long add(Notice notice); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - public abstract void addAll(List noticeList); - - @Query("DELETE FROM notices WHERE profileId = :profileId") - public abstract void clear(int profileId); - - @Query("DELETE FROM notices WHERE profileId = :profileId AND noticeSemester = :semester") - public abstract void clearForSemester(int profileId, int semester); - - @RawQuery(observedEntities = {Notice.class}) - abstract LiveData> getAll(SupportSQLiteQuery query); - public LiveData> getAll(int profileId, String filter) { - return getAll(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM notices \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON noticeId = thingId AND thingType = "+TYPE_NOTICE+" AND metadata.profileId = "+profileId+"\n" + - "WHERE notices.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public LiveData> getAll(int profileId) { - return getAll(profileId, "1"); - } - public LiveData> getAllWhere(int profileId, String filter) { - return getAll(profileId, filter); - } - - @RawQuery(observedEntities = {Notice.class, Metadata.class}) - abstract List getAllNow(SupportSQLiteQuery query); - public List getAllNow(int profileId, String filter) { - return getAllNow(new SimpleSQLiteQuery("SELECT \n" + - "*, \n" + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName\n" + - "FROM notices \n" + - "LEFT JOIN teachers USING(profileId, teacherId)\n" + - "LEFT JOIN metadata ON noticeId = thingId AND thingType = "+TYPE_NOTICE+" AND metadata.profileId = "+profileId+"\n" + - "WHERE notices.profileId = "+profileId+" AND "+filter+"\n" + - "ORDER BY addedDate DESC")); - } - public List getNotNotifiedNow(int profileId) { - return getAllNow(profileId, "notified = 0"); - } - - @Query("SELECT " + - "*, " + - "teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName " + - "FROM notices " + - "LEFT JOIN teachers USING(profileId, teacherId) " + - "LEFT JOIN metadata ON noticeId = thingId AND thingType = "+TYPE_NOTICE+" AND metadata.profileId = notices.profileId " + - "WHERE notified = 0 " + - "ORDER BY addedDate DESC") - public abstract List getNotNotifiedNow(); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.kt new file mode 100644 index 00000000..79bae175 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/NoticeDao.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.full.NoticeFull + +@Dao +@SelectiveDao(db = AppDb::class) +abstract class NoticeDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName + FROM notices + LEFT JOIN teachers USING(profileId, teacherId) + LEFT JOIN metadata ON noticeId = thingId AND thingType = ${Metadata.TYPE_NOTICE} AND metadata.profileId = notices.profileId + """ + + private const val ORDER_BY = """ORDER BY addedDate DESC""" + } + + private val selective by lazy { NoticeDaoSelective(App.db) } + + @RawQuery(observedEntities = [Notice::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Notice::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData + + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "noticeId"], skippedColumns = ["addedDate"]) + override fun update(item: Notice) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + + // CLEAR + @Query("DELETE FROM notices WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM notices WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE notices.profileId = $profileId $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE notices.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE notices.profileId = $profileId AND notified = 0 $ORDER_BY") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE notices.profileId = $profileId AND noticeId = $id") + + @Query("UPDATE notices SET keep = 0 WHERE profileId = :profileId AND noticeSemester = :semester") + abstract fun dontKeepSemester(profileId: Int, semester: Int) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt index 1d4881bb..1528adf1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt @@ -1,65 +1,74 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ - package pl.szczodrzynski.edziennik.data.db.dao import androidx.lifecycle.LiveData import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.TeacherAbsence import pl.szczodrzynski.edziennik.data.db.full.TeacherAbsenceFull import pl.szczodrzynski.edziennik.utils.models.Date @Dao -interface TeacherAbsenceDao { +@SelectiveDao(db = AppDb::class) +abstract class TeacherAbsenceDao : BaseDao { + companion object { + private const val QUERY = """ + SELECT + *, + teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName + FROM teacherAbsence + LEFT JOIN teachers USING(profileId, teacherId) + LEFT JOIN metadata ON teacherAbsenceId = thingId AND thingType = ${Metadata.TYPE_TEACHER_ABSENCE} AND metadata.profileId = teacherAbsence.profileId + """ - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun add(teacherAbsence: TeacherAbsence) + private const val ORDER_BY = """ORDER BY teacherAbsenceDateFrom ASC""" + } - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun addAll(teacherAbsenceList: List) + private val selective by lazy { TeacherAbsenceDaoSelective(App.db) } - @Query("SELECT * FROM teacherAbsence WHERE profileId = :profileId") - fun getAll(profileId: Int): List + @RawQuery(observedEntities = [TeacherAbsence::class]) + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [TeacherAbsence::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData - @Query("SELECT *, teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName, " + - "metadata.seen, metadata.notified, metadata.addedDate FROM teacherAbsence " + - "LEFT JOIN teachers USING (profileId, teacherId) " + - "LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = " + Metadata.TYPE_TEACHER_ABSENCE + - " AND metadata.profileId = :profileId WHERE teachers.profileId = :profileId") - fun getAllFullNow(profileId: Int): List - - @Query("SELECT *, teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName, " + - "metadata.seen, metadata.notified, metadata.addedDate FROM teacherAbsence " + - "LEFT JOIN teachers USING (profileId, teacherId) " + - "LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = " + Metadata.TYPE_TEACHER_ABSENCE + - " AND metadata.profileId = :profileId WHERE teachers.profileId = :profileId " + - "AND :date BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo") - fun getAllByDateFull(profileId: Int, date: Date): LiveData> - - @Query("SELECT *, teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName, " + - "metadata.seen, metadata.notified, metadata.addedDate FROM teacherAbsence " + - "LEFT JOIN teachers USING (profileId, teacherId) " + - "LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = " + Metadata.TYPE_TEACHER_ABSENCE + - " AND metadata.profileId = :profileId WHERE teachers.profileId = :profileId " + - "AND :date BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo") - fun getAllByDateNow(profileId: Int, date: Date): List - - @Query(""" - SELECT *, - teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName - FROM teacherAbsence - LEFT JOIN teachers USING (profileId, teacherId) - LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = ${Metadata.TYPE_TEACHER_ABSENCE} - AND teachers.profileId = metadata.profileId WHERE metadata.notified = 0 - ORDER BY addedDate DESC - """) - fun getNotNotifiedNow(): List + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "teacherAbsenceId"], skippedColumns = ["addedDate"]) + override fun update(item: TeacherAbsence) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) + // CLEAR @Query("DELETE FROM teacherAbsence WHERE profileId = :profileId") - fun clear(profileId: Int) + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM teacherAbsence WHERE keep = 0") + abstract override fun removeNotKept() + + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE teacherAbsence.profileId = $profileId $ORDER_BY") + fun getAllByDate(profileId: Int, date: Date) = + getRaw("$QUERY WHERE teacherAbsence.profileId = $profileId AND '${date.stringY_m_d}' BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo $ORDER_BY") + + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE teacherAbsence.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE teacherAbsence.profileId = $profileId AND notified = 0 $ORDER_BY") + fun getAllByDateNow(profileId: Int, date: Date) = + getRawNow("$QUERY WHERE teacherAbsence.profileId = $profileId AND '${date.stringY_m_d}' BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo $ORDER_BY") + + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE teacherAbsence.profileId = $profileId AND teacherAbsenceId = $id") } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt index 9ac9709b..cc236dc6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt @@ -1,20 +1,25 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ - package pl.szczodrzynski.edziennik.data.db.dao import androidx.lifecycle.LiveData -import androidx.room.* -import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.room.Dao +import androidx.room.Query +import androidx.room.RawQuery import androidx.sqlite.db.SupportSQLiteQuery +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.utils.models.Date @Dao -interface TimetableDao { +@SelectiveDao(db = AppDb::class) +abstract class TimetableDao : BaseDao { companion object { private const val QUERY = """ SELECT @@ -25,7 +30,7 @@ interface TimetableDao { oldS.subjectLongName AS oldSubjectName, oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName, oldG.teamName AS oldTeamName, - metadata.seen, metadata.notified, metadata.addedDate + metadata.seen, metadata.notified FROM timetable LEFT JOIN subjects USING(profileId, subjectId) LEFT JOIN teachers USING(profileId, teacherId) @@ -35,111 +40,77 @@ interface TimetableDao { LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId """ + + private const val ORDER_BY = """ORDER BY profileId, id, type""" + private const val IS_CHANGED = """type != -1 AND type != 0""" } - @Insert(onConflict = OnConflictStrategy.REPLACE) - operator fun plusAssign(lessonList: List) - - @Query("DELETE FROM timetable WHERE profileId = :profileId") - fun clear(profileId: Int) - - @Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))") - fun clearFromDate(profileId: Int, dateFrom: Date) - - @Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo))") - fun clearToDate(profileId: Int, dateTo: Date) - - @Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))") - fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date) + private val selective by lazy { TimetableDaoSelective(App.db) } @RawQuery(observedEntities = [Lesson::class]) - fun getRaw(query: SupportSQLiteQuery): LiveData> + abstract override fun getRaw(query: SupportSQLiteQuery): LiveData> + @RawQuery(observedEntities = [Lesson::class]) + abstract override fun getOne(query: SupportSQLiteQuery): LiveData - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND type != -1 AND type != 0 - ORDER BY id, type - """) - fun getAllChangesNow(profileId: Int): List + // SELECTIVE UPDATE + @UpdateSelective(primaryKeys = ["profileId", "id"], skippedColumns = ["addedDate"]) + override fun update(item: Lesson) = selective.update(item) + override fun updateAll(items: List) = selective.updateAll(items) - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND type != -1 AND type != 0 AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)) - ORDER BY id, type - """) - fun getChangesForDateNow(profileId: Int, date: Date): List + // CLEAR + @Query("DELETE FROM timetable WHERE profileId = :profileId") + abstract override fun clear(profileId: Int) + // REMOVE NOT KEPT + @Query("DELETE FROM timetable WHERE keep = 0") + abstract override fun removeNotKept() - fun getForDate(profileId: Int, date: Date) = getRaw(SimpleSQLiteQuery(""" - $QUERY - WHERE timetable.profileId = $profileId AND ((type != 3 AND date = "${date.stringY_m_d}") OR ((type = 3 OR type = 1) AND oldDate = "${date.stringY_m_d}")) - ORDER BY id, type - """)) + // GET ALL - LIVE DATA + fun getAll(profileId: Int) = + getRaw("$QUERY WHERE timetable.profileId = $profileId $ORDER_BY") + fun getAllForDate(profileId: Int, date: Date) = + getRaw("$QUERY WHERE timetable.profileId = $profileId AND ((type != 3 AND date = '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate = '${date.stringY_m_d}')) $ORDER_BY") + fun getNextWithSubject(profileId: Int, date: Date, subjectId: Long) = + getOne("$QUERY " + + "WHERE timetable.profileId = $profileId " + + "AND ((type != 3 AND date > '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate > '${date.stringY_m_d}')) " + + "AND timetable.subjectId = $subjectId " + + "LIMIT 1") + fun getNextWithSubjectAndTeam(profileId: Int, date: Date, subjectId: Long, teamId: Long) = + getOne("$QUERY " + + "WHERE timetable.profileId = $profileId " + + "AND ((type != 3 AND date > '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate > '${date.stringY_m_d}')) " + + "AND timetable.subjectId = $subjectId " + + "AND timetable.teamId = $teamId " + + "LIMIT 1") + fun getBetweenDates(dateFrom: Date, dateTo: Date) = + getRaw("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY") - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)) - ORDER BY id, type - """) - fun getForDateNow(profileId: Int, date: Date): List + // GET ALL - NOW + fun getAllNow(profileId: Int) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId $ORDER_BY") + fun getNotNotifiedNow() = + getRawNow("$QUERY WHERE notified = 0 AND timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) $ORDER_BY") + fun getNotNotifiedNow(profileId: Int) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId AND notified = 0 AND timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) $ORDER_BY") + fun getAllForDateNow(profileId: Int, date: Date) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId AND ((type != 3 AND date = '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate = '${date.stringY_m_d}')) $ORDER_BY") + fun getChangesNow(profileId: Int) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId AND $IS_CHANGED $ORDER_BY") + fun getChangesForDateNow(profileId: Int, date: Date) = + getRawNow("$QUERY WHERE timetable.profileId = $profileId AND $IS_CHANGED AND ((type != 3 AND date = '${date.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate = '${date.stringY_m_d}')) $ORDER_BY") + fun getBetweenDatesNow(dateFrom: Date, dateTo: Date) = + getRawNow("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY") - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId - ORDER BY id, type - LIMIT 1 - """) - fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long): LiveData + // GET ONE - NOW + fun getByIdNow(profileId: Int, id: Long) = + getOneNow("$QUERY WHERE timetable.profileId = $profileId AND timetable.id = $id") - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId AND timetable.teamId = :teamId - ORDER BY id, type - LIMIT 1 - """) - fun getNextWithSubjectAndTeam(profileId: Int, today: Date, subjectId: Long, teamId: Long): LiveData + @Query("UPDATE timetable SET keep = 0 WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))") + abstract fun dontKeepFromDate(profileId: Int, dateFrom: Date) - @Query(""" - $QUERY - WHERE (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo) - ORDER BY profileId, id, type - """) - fun getBetweenDatesNow(dateFrom: Date, dateTo: Date): List + @Query("UPDATE timetable SET keep = 0 WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo))") + abstract fun dontKeepToDate(profileId: Int, dateTo: Date) - @Query(""" - $QUERY - WHERE (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo) - ORDER BY profileId, id, type - """) - fun getBetweenDates(dateFrom: Date, dateTo: Date): LiveData> - - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND timetable.id = :lessonId - ORDER BY id, type - """) - fun getByIdNow(profileId: Int, lessonId: Long): LessonFull? - - @Query(""" - $QUERY - WHERE timetable.profileId = :profileId AND timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) AND metadata.notified = 0 - """) - fun getNotNotifiedNow(profileId: Int): List - - @Query(""" - SELECT - timetable.*, - subjects.subjectLongName AS subjectName, - teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName, - oldS.subjectLongName AS oldSubjectName, - oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName, - metadata.seen, metadata.notified, metadata.addedDate - FROM timetable - LEFT JOIN subjects USING(profileId, subjectId) - LEFT JOIN teachers USING(profileId, teacherId) - LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId - LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId - LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId - WHERE timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) AND metadata.notified = 0 - """) - fun getNotNotifiedNow(): List + @Query("UPDATE timetable SET keep = 0 WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))") + abstract fun dontKeepBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.java deleted file mode 100644 index af17e0c3..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.entity; - -import androidx.annotation.Nullable; -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.Ignore; -import androidx.room.Index; - -import pl.szczodrzynski.edziennik.utils.models.Date; - -@Entity(tableName = "announcements", - primaryKeys = {"profileId", "announcementId"}, - indices = {@Index(value = {"profileId"})}) -public class Announcement { - public int profileId; - - @ColumnInfo(name = "announcementId") - public long id; - - @ColumnInfo(name = "announcementSubject") - public String subject; - @Nullable - @ColumnInfo(name = "announcementText") - public String text; - @Nullable - @ColumnInfo(name = "announcementStartDate") - public Date startDate; - @Nullable - @ColumnInfo(name = "announcementEndDate") - public Date endDate; - - public long teacherId; - - @Nullable - @ColumnInfo(name = "announcementIdString") - public String idString; - - @Ignore - public Announcement() {} - - public Announcement(int profileId, long id, String subject, String text, @Nullable Date startDate, @Nullable Date endDate, long teacherId, @Nullable String idString) { - this.profileId = profileId; - this.id = id; - this.subject = subject; - this.text = text; - this.startDate = startDate; - this.endDate = endDate; - this.teacherId = teacherId; - this.idString = idString; - } -} - - - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.kt new file mode 100644 index 00000000..82f7bb78 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Announcement.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.Index +import pl.szczodrzynski.edziennik.utils.models.Date + +@Entity(tableName = "announcements", + primaryKeys = ["profileId", "announcementId"], + indices = [ + Index(value = ["profileId"]) + ]) +open class Announcement( + val profileId: Int, + @ColumnInfo(name = "announcementId") + var id: Long, + @ColumnInfo(name = "announcementSubject") + var subject: String, + @ColumnInfo(name = "announcementText") + var text: String?, + + @ColumnInfo(name = "announcementStartDate") + var startDate: Date?, + @ColumnInfo(name = "announcementEndDate") + var endDate: Date?, + + var teacherId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { + + @ColumnInfo(name = "announcementIdString") + var idString: String? = null + + @Ignore + var showAsUnseen: Boolean? = null +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.java deleted file mode 100644 index 039dcb0c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.entity; - -import androidx.annotation.NonNull; -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.Ignore; -import androidx.room.Index; - -import pl.szczodrzynski.edziennik.utils.models.Date; -import pl.szczodrzynski.edziennik.utils.models.Time; - -@Entity(tableName = "attendances", - primaryKeys = {"profileId", "attendanceId", "attendanceLessonDate", "attendanceStartTime"}, - indices = {@Index(value = {"profileId"})}) -public class Attendance { - public int profileId; - - @ColumnInfo(name = "attendanceId") - public long id; - - @NonNull - @ColumnInfo(name = "attendanceLessonDate") - public Date lessonDate; - @NonNull - @ColumnInfo(name = "attendanceStartTime") - public Time startTime; - @ColumnInfo(name = "attendanceLessonTopic") - public String lessonTopic; - @ColumnInfo(name = "attendanceSemester") - public int semester; - public static final int TYPE_PRESENT = 0; - public static final int TYPE_ABSENT = 1; - public static final int TYPE_ABSENT_EXCUSED = 2; - public static final int TYPE_RELEASED = 3; - public static final int TYPE_BELATED = 4; - public static final int TYPE_BELATED_EXCUSED = 5; - public static final int TYPE_CUSTOM = 6; - public static final int TYPE_DAY_FREE = 7; - @ColumnInfo(name = "attendanceType") - public int type = TYPE_PRESENT; - - public long teacherId; - public long subjectId; - - @Ignore - public Attendance() { - this(-1, -1, -1, -1, 0, "", Date.getToday(), Time.getNow(), TYPE_PRESENT); - } - - public Attendance(int profileId, long id, long teacherId, long subjectId, int semester, String lessonTopic, Date lessonDate, Time startTime, int type) { - this.profileId = profileId; - this.id = id; - this.teacherId = teacherId; - this.subjectId = subjectId; - this.semester = semester; - this.lessonTopic = lessonTopic; - this.lessonDate = lessonDate; - this.startTime = startTime; - this.type = type; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt new file mode 100644 index 00000000..af165b07 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.Index +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +@Entity(tableName = "attendances", + primaryKeys = ["profileId", "attendanceId"], + indices = [ + Index(value = ["profileId"]) + ]) +open class Attendance( + val profileId: Int, + @ColumnInfo(name = "attendanceId") + var id: Long, + /** Base type ID used to count attendance stats */ + @ColumnInfo(name = "attendanceBaseType") + var baseType: Int, + /** A full type name coming from the e-register */ + @ColumnInfo(name = "attendanceTypeName") + var typeName: String, + /** A short name to display by default, might be different for non-standard types */ + @ColumnInfo(name = "attendanceTypeShort") + val typeShort: String, + /** A short name that the e-register would display */ + @ColumnInfo(name = "attendanceTypeSymbol") + var typeSymbol: String, + /** A color that the e-register would display, null falls back to app's default */ + @ColumnInfo(name = "attendanceTypeColor") + var typeColor: Int?, + + @ColumnInfo(name = "attendanceDate") + var date: Date, + @ColumnInfo(name = "attendanceTime") + var startTime: Time?, + @ColumnInfo(name = "attendanceSemester") + var semester: Int, + + var teacherId: Long, + var subjectId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { + companion object { + const val TYPE_UNKNOWN = -1 + const val TYPE_PRESENT = 0 + const val TYPE_PRESENT_CUSTOM = 10 // count as presence AND show in the list + const val TYPE_ABSENT = 1 + const val TYPE_ABSENT_EXCUSED = 2 + const val TYPE_RELEASED = 3 + const val TYPE_BELATED = 4 + const val TYPE_BELATED_EXCUSED = 5 + const val TYPE_DAY_FREE = 6 + } + + @ColumnInfo(name = "attendanceLessonTopic") + var lessonTopic: String? = null + @ColumnInfo(name = "attendanceLessonNumber") + var lessonNumber: Int? = null + + @Ignore + var showAsUnseen = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt index 0e3e86f9..f70c7bc6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt @@ -9,15 +9,16 @@ import androidx.room.Entity @Entity(tableName = "attendanceTypes", primaryKeys = ["profileId", "id"]) data class AttendanceType ( - val profileId: Int, - val id: Long, - - val name: String, - - val type: Int, - - val color: Int - + /** Base type ID used to count attendance stats */ + val baseType: Int, + /** A full type name coming from the e-register */ + val typeName: String, + /** A short name to display by default, might be different for non-standard types */ + val typeShort: String, + /** A short name that the e-register would display */ + val typeSymbol: String, + /** A color that the e-register would display, null falls back to app's default */ + val typeColor: Int? ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt index b811ff61..9d13e883 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt @@ -41,7 +41,8 @@ open class Event( var teacherId: Long, var subjectId: Long, - var teamId: Long + var teamId: Long, + var addedDate: Long = System.currentTimeMillis() ) : Keepable() { companion object { const val TYPE_UNDEFINED = -2L diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Grade.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Grade.kt index 393ab332..5bf3bd86 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Grade.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Grade.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-24. */ package pl.szczodrzynski.edziennik.data.db.entity @@ -8,23 +8,11 @@ import androidx.room.Entity import androidx.room.Ignore import androidx.room.Index -/*public Grade(int profileId, long id, String category, int color, String description, String name, float value, float weight, int semester, long teacherId, long subjectId) { - this.profileId = profileId; - this.id = id; - this.category = category; - this.color = color; - this.description = description; - this.name = name; - this.value = value; - this.weight = weight; - this.semester = semester; - this.teacherId = teacherId; - this.subjectId = subjectId; - }*/ - @Entity(tableName = "grades", primaryKeys = ["profileId", "gradeId"], - indices = [Index(value = ["profileId"])]) + indices = [ + Index(value = ["profileId"]) + ]) open class Grade( val profileId: Int, @ColumnInfo(name = "gradeId") @@ -40,6 +28,7 @@ open class Grade( var weight: Float, @ColumnInfo(name = "gradeColor") var color: Int, + @ColumnInfo(name = "gradeCategory") var category: String?, @ColumnInfo(name = "gradeDescription") @@ -50,8 +39,9 @@ open class Grade( @ColumnInfo(name = "gradeSemester") val semester: Int, val teacherId: Long, - val subjectId: Long -) { + val subjectId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { companion object { const val TYPE_NORMAL = 0 const val TYPE_SEMESTER1_PROPOSED = 1 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt index ceb6cf9c..0c97ba24 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt @@ -1,7 +1,6 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ - package pl.szczodrzynski.edziennik.data.db.entity import androidx.room.Entity @@ -11,12 +10,15 @@ import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @Entity(tableName = "timetable", + primaryKeys = ["profileId", "id"], indices = [ Index(value = ["profileId", "type", "date"]), Index(value = ["profileId", "type", "oldDate"]) - ], - primaryKeys = ["profileId", "id"]) -open class Lesson(val profileId: Int, var id: Long) { + ]) +open class Lesson( + val profileId: Int, + var id: Long +) : Keepable() { companion object { const val TYPE_NO_LESSONS = -1 const val TYPE_NORMAL = 0 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.java deleted file mode 100644 index bca49ec5..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.entity; - -import androidx.annotation.NonNull; -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.Ignore; - -import pl.szczodrzynski.edziennik.utils.models.Date; - -@Entity(tableName = "luckyNumbers", - primaryKeys = {"profileId", "luckyNumberDate"}) -public class LuckyNumber { - public int profileId; - - @NonNull - @ColumnInfo(name = "luckyNumberDate", typeAffinity = 3) - public Date date; - @ColumnInfo(name = "luckyNumber") - public int number; - - public LuckyNumber(int profileId, @NonNull Date date, int number) { - this.profileId = profileId; - this.date = date; - this.number = number; - } - - @Ignore - public LuckyNumber() { - this.date = Date.getToday(); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.kt new file mode 100644 index 00000000..6fb943c9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/LuckyNumber.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import pl.szczodrzynski.edziennik.utils.models.Date + +@Entity(tableName = "luckyNumbers", + primaryKeys = ["profileId", "luckyNumberDate"]) +open class LuckyNumber( + val profileId: Int, + @ColumnInfo(name = "luckyNumberDate", typeAffinity = ColumnInfo.INTEGER) + var date: Date, + @ColumnInfo(name = "luckyNumber") + var number: Int +) : Keepable() { + @Ignore + var showAsUnseen = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Message.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Message.kt index 1cf9b3ba..98105057 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Message.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Message.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ package pl.szczodrzynski.edziennik.data.db.entity @@ -30,7 +30,8 @@ open class Message( * Keep in mind that this being null does NOT * necessarily mean the message is sent. */ - var senderId: Long? + var senderId: Long?, + var addedDate: Long = System.currentTimeMillis() ) : Keepable() { companion object { const val TYPE_RECEIVED = 0 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Metadata.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Metadata.java index 6ea94f4e..7374d915 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Metadata.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Metadata.java @@ -36,7 +36,6 @@ public class Metadata { public boolean seen; public boolean notified; - public long addedDate; @Ignore public Metadata() { @@ -45,13 +44,12 @@ public class Metadata { this.notified = false; } - public Metadata(int profileId, int thingType, long thingId, boolean seen, boolean notified, long addedDate) { + public Metadata(int profileId, int thingType, long thingId, boolean seen, boolean notified) { this.profileId = profileId; this.thingType = thingType; this.thingId = thingId; this.seen = seen; this.notified = notified; - this.addedDate = addedDate; } public String thingType() { @@ -86,7 +84,6 @@ public class Metadata { ", thingId=" + thingId + ", seen=" + seen + ", notified=" + notified + - ", addedDate=" + addedDate + '}'; } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.java deleted file mode 100644 index 306e6f92..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.entity; - -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.Ignore; -import androidx.room.Index; - -@Entity(tableName = "notices", - primaryKeys = {"profileId", "noticeId"}, - indices = {@Index(value = {"profileId"})}) -public class Notice { - public int profileId; - - @ColumnInfo(name = "noticeId") - public long id; - - @ColumnInfo(name = "noticeText") - public String text; - @ColumnInfo(name = "noticeSemester") - public int semester; - public static final int TYPE_NEUTRAL = 0; - public static final int TYPE_POSITIVE = 1; - public static final int TYPE_NEGATIVE = 2; - @ColumnInfo(name = "noticeType") - public int type = TYPE_NEUTRAL; - - public float points = 0; - public String category = null; - - public long teacherId; - - @Ignore - public Notice() {} - - public Notice(int profileId, long id, String text, int semester, int type, long teacherId) { - this.profileId = profileId; - this.id = id; - this.text = text; - this.semester = semester; - this.type = type; - this.teacherId = teacherId; - } -} - - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.kt new file mode 100644 index 00000000..0e1f21fe --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notice.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.Index + +@Entity(tableName = "notices", + primaryKeys = ["profileId", "noticeId"], + indices = [ + Index(value = ["profileId"]) + ]) +open class Notice( + val profileId: Int, + @ColumnInfo(name = "noticeId") + var id: Long, + @ColumnInfo(name = "noticeType") + var type: Int, + @ColumnInfo(name = "noticeSemester") + var semester: Int, + + @ColumnInfo(name = "noticeText") + var text: String, + @ColumnInfo(name = "noticeCategory") + var category: String?, + @ColumnInfo(name = "noticePoints") + var points: Float?, + + var teacherId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { + companion object { + const val TYPE_NEUTRAL = 0 + const val TYPE_POSITIVE = 1 + const val TYPE_NEGATIVE = 2 + } + + @Ignore + var showAsUnseen = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TeacherAbsence.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TeacherAbsence.kt index af037851..9ec5f828 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TeacherAbsence.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TeacherAbsence.kt @@ -1,37 +1,41 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ package pl.szczodrzynski.edziennik.data.db.entity import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.Index import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @Entity(tableName = "teacherAbsence", - primaryKeys = ["profileId", "teacherAbsenceId"]) -open class TeacherAbsence ( + primaryKeys = ["profileId", "teacherAbsenceId"], + indices = [ + Index(value = ["profileId"]) + ]) +open class TeacherAbsence( + val profileId: Int, + @ColumnInfo(name = "teacherAbsenceId") + val id: Long, + @ColumnInfo(name = "teacherAbsenceType") + val type: Long, + @ColumnInfo(name = "teacherAbsenceName") + val name: String?, - val profileId: Int, + @ColumnInfo(name = "teacherAbsenceDateFrom") + val dateFrom: Date, + @ColumnInfo(name = "teacherAbsenceDateTo") + val dateTo: Date, + @ColumnInfo(name = "teacherAbsenceTimeFrom") + val timeFrom: Time?, + @ColumnInfo(name = "teacherAbsenceTimeTo") + val timeTo: Time?, - @ColumnInfo(name = "teacherAbsenceId") - val id: Long, - - val teacherId: Long, - - @ColumnInfo(name = "teacherAbsenceType") - val type: Long, - - @ColumnInfo(name = "teacherAbsenceName") - val name: String?, - - @ColumnInfo(name = "teacherAbsenceDateFrom") - val dateFrom: Date, - - @ColumnInfo(name = "teacherAbsenceDateTo") - val dateTo: Date, - - @ColumnInfo(name = "teacherAbsenceTimeFrom") - val timeFrom: Time?, - - @ColumnInfo(name = "teacherAbsenceTimeTo") - val timeTo: Time? - -) + val teacherId: Long, + var addedDate: Long = System.currentTimeMillis() +) : Keepable() { + @Ignore + var showAsUnseen = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.java deleted file mode 100644 index 2811a0c7..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.full; - -import pl.szczodrzynski.edziennik.data.db.entity.Announcement; - -public class AnnouncementFull extends Announcement { - public String teacherFullName = ""; - - // metadata - public boolean seen; - public boolean notified; - public long addedDate; -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.kt new file mode 100644 index 00000000..5b4cb711 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AnnouncementFull.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.full + +import pl.szczodrzynski.edziennik.data.db.entity.Announcement +import pl.szczodrzynski.edziennik.utils.models.Date + +class AnnouncementFull( + profileId: Int, id: Long, + subject: String, text: String?, + startDate: Date?, endDate: Date?, + teacherId: Long, addedDate: Long = System.currentTimeMillis() +) : Announcement( + profileId, id, + subject, text, + startDate, endDate, + teacherId, addedDate +) { + var teacherName: String? = null + + // metadata + var seen = false + var notified = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.java deleted file mode 100644 index 605d06b2..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.full; - -import pl.szczodrzynski.edziennik.data.db.entity.Attendance; - -public class AttendanceFull extends Attendance { - public String teacherFullName = ""; - - public String subjectLongName = ""; - public String subjectShortName = ""; - - // metadata - public boolean seen; - public boolean notified; - public long addedDate; -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt new file mode 100644 index 00000000..223ceb38 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-24. + */ +package pl.szczodrzynski.edziennik.data.db.full + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class AttendanceFull( + profileId: Int, id: Long, + baseType: Int, typeName: String, typeShort: String, typeSymbol: String, typeColor: Int?, + date: Date, startTime: Time, semester: Int, + teacherId: Long, subjectId: Long, addedDate: Long = System.currentTimeMillis() +) : Attendance( + profileId, id, + baseType, typeName, typeShort, typeSymbol, typeColor, + date, startTime, semester, + teacherId, subjectId, addedDate +) { + var teacherName: String? = null + var subjectLongName: String? = null + var subjectShortName: String? = null + + // metadata + var seen = false + var notified = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt index a3dcc2cb..01e85395 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ package pl.szczodrzynski.edziennik.data.db.full @@ -11,16 +11,16 @@ import pl.szczodrzynski.edziennik.utils.models.Time class EventFull( profileId: Int, id: Long, date: Date, time: Time?, topic: String, color: Int?, type: Long, - teacherId: Long, subjectId: Long, teamId: Long + teacherId: Long, subjectId: Long, teamId: Long, addedDate: Long = System.currentTimeMillis() ) : Event( profileId, id, date, time, topic, color, type, - teacherId, subjectId, teamId + teacherId, subjectId, teamId, addedDate ) { constructor(event: Event, metadata: Metadata? = null) : this( event.profileId, event.id, event.date, event.time, event.topic, event.color, event.type, - event.teacherId, event.subjectId, event.teamId) { + event.teacherId, event.subjectId, event.teamId, event.addedDate) { event.let { addedManually = it.addedManually sharedBy = it.sharedBy @@ -33,7 +33,6 @@ class EventFull( metadata?.let { seen = it.seen notified = it.notified - addedDate = it.addedDate } } @@ -45,10 +44,10 @@ class EventFull( var subjectShortName: String? = null var teamName: String? = null var teamCode: String? = null + // metadata var seen = false var notified = false - var addedDate: Long = 0 val eventColor get() = color ?: typeColor ?: 0xff2196f3.toInt() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/GradeFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/GradeFull.kt index 8cc80974..f8d90c05 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/GradeFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/GradeFull.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-24. */ package pl.szczodrzynski.edziennik.data.db.full @@ -9,18 +9,18 @@ class GradeFull( profileId: Int, id: Long, name: String, type: Int, value: Float, weight: Float, color: Int, category: String?, description: String?, comment: String?, - semester: Int, teacherId: Long, subjectId: Long + semester: Int, teacherId: Long, subjectId: Long, addedDate: Long = System.currentTimeMillis() ) : Grade( profileId, id, name, type, value, weight, color, category, description, comment, - semester, teacherId, subjectId + semester, teacherId, subjectId, addedDate ) { + var teacherName: String? = null var subjectLongName: String? = null var subjectShortName: String? = null - var teacherFullName: String? = null + // metadata var seen = false var notified = false - var addedDate: Long = 0 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LessonFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LessonFull.kt index 5661a614..0e9542b6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LessonFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LessonFull.kt @@ -1,3 +1,6 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ package pl.szczodrzynski.edziennik.data.db.full import android.content.Context @@ -5,7 +8,11 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.utils.models.Time -class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) { +class LessonFull( + profileId: Int, id: Long +) : Lesson( + profileId, id +) { var subjectName: String? = null var teacherName: String? = null var teamName: String? = null @@ -126,5 +133,4 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) { // metadata var seen: Boolean = false var notified: Boolean = false - var addedDate: Long = 0 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.java deleted file mode 100644 index d73cb95f..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.full; - -import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber; - -public class LuckyNumberFull extends LuckyNumber { - // metadata - public boolean seen; - public boolean notified; - public long addedDate; -} - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.kt new file mode 100644 index 00000000..bb24724a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/LuckyNumberFull.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.full + +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.utils.models.Date + +class LuckyNumberFull( + profileId: Int, date: Date, + number: Int +) : LuckyNumber( + profileId, date, + number +) { + // metadata + var seen = false + var notified = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt index 5b873b7a..3fdcdd0d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + * Copyright (c) Kuba Szczodrzyński 2020-4-25. */ package pl.szczodrzynski.edziennik.data.db.full @@ -10,10 +10,12 @@ import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient class MessageFull( profileId: Int, id: Long, type: Int, - subject: String, body: String?, senderId: Long? + subject: String, body: String?, senderId: Long?, + addedDate: Long = System.currentTimeMillis() ) : Message( profileId, id, type, - subject, body, senderId + subject, body, senderId, + addedDate ) { var senderName: String? = null @Relation(parentColumn = "messageId", entityColumn = "messageId", entity = MessageRecipient::class) @@ -33,5 +35,4 @@ class MessageFull( // metadata var seen = false var notified = false - var addedDate: Long = 0 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.java deleted file mode 100644 index 372650fd..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.full; - -import pl.szczodrzynski.edziennik.data.db.entity.Notice; - -public class NoticeFull extends Notice { - public String teacherFullName = ""; - - // metadata - public boolean seen; - public boolean notified; - public long addedDate; -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.kt new file mode 100644 index 00000000..25aee14d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/NoticeFull.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ +package pl.szczodrzynski.edziennik.data.db.full + +import pl.szczodrzynski.edziennik.data.db.entity.Notice + +class NoticeFull( + profileId: Int, id: Long, type: Int, semester: Int, + text: String, category: String?, points: Float?, + teacherId: Long, addedDate: Long = System.currentTimeMillis() +) : Notice( + profileId, id, type, semester, + text, category, points, + teacherId, addedDate +) { + var teacherName: String? = null + + // metadata + var seen = false + var notified = false +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/TeacherAbsenceFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/TeacherAbsenceFull.kt index e9d53a59..39a88e0f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/TeacherAbsenceFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/TeacherAbsenceFull.kt @@ -1,17 +1,24 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-25. + */ package pl.szczodrzynski.edziennik.data.db.full import pl.szczodrzynski.edziennik.data.db.entity.TeacherAbsence import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time -class TeacherAbsenceFull(profileId: Int, id: Long, teacherId: Long, type: Long, name: String?, - dateFrom: Date, dateTo: Date, timeFrom: Time?, timeTo: Time?) - : TeacherAbsence(profileId, id, teacherId, type, name, dateFrom, dateTo, timeFrom, timeTo) { - - var teacherFullName = "" +class TeacherAbsenceFull( + profileId: Int, id: Long, type: Long, name: String?, + dateFrom: Date, dateTo: Date, timeFrom: Time?, timeTo: Time?, + teacherId: Long, addedDate: Long = System.currentTimeMillis() +) : TeacherAbsence( + profileId, id, type, name, + dateFrom, dateTo, timeFrom, timeTo, + teacherId, addedDate +) { + var teacherName: String? = null // metadata var seen: Boolean = false var notified: Boolean = false - var addedDate: Long = 0 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt new file mode 100644 index 00000000..6d505edf --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt @@ -0,0 +1,176 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-29. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration86 : Migration(85, 86) { + override fun migrate(database: SupportSQLiteDatabase) { + // Migrating some models, moving addedDate from metadata to entities + + // Announcements + database.execSQL("""ALTER TABLE announcements RENAME TO _announcements""") + database.execSQL("""CREATE TABLE `announcements` ( + `announcementIdString` TEXT, + `profileId` INTEGER NOT NULL, + `announcementId` INTEGER NOT NULL, + `announcementSubject` TEXT NOT NULL, + `announcementText` TEXT, + `announcementStartDate` TEXT, + `announcementEndDate` TEXT, + `teacherId` INTEGER NOT NULL, + `addedDate` INTEGER NOT NULL, + `keep` INTEGER NOT NULL, + PRIMARY KEY(`profileId`, `announcementId`) + )""") + database.execSQL("""DROP INDEX IF EXISTS index_announcements_profileId""") + database.execSQL("""CREATE INDEX `index_announcements_profileId` ON `announcements` (`profileId`)""") + database.execSQL("""REPLACE INTO announcements ( + announcementIdString, profileId, announcementId, announcementSubject, announcementText, announcementStartDate, announcementEndDate, teacherId, addedDate, keep + ) SELECT + announcementIdString, profileId, announcementId, IFNULL(announcementSubject, ""), announcementText, announcementStartDate, announcementEndDate, teacherId, 0, 1 + FROM _announcements""") + database.execSQL("""DROP TABLE _announcements""") + + // Attendance Types + database.execSQL("""ALTER TABLE attendanceTypes RENAME TO _attendanceTypes""") + database.execSQL("""CREATE TABLE `attendanceTypes` ( + `profileId` INTEGER NOT NULL, + `id` INTEGER NOT NULL, + `baseType` INTEGER NOT NULL, + `typeName` TEXT NOT NULL, + `typeShort` TEXT NOT NULL, + `typeSymbol` TEXT NOT NULL, + `typeColor` INTEGER, + PRIMARY KEY(`profileId`, `id`) + )""") + database.execSQL("""REPLACE INTO attendanceTypes ( + profileId, id, + baseType, + typeName, + typeShort, + typeSymbol, + typeColor + ) SELECT + profileId, id, + CASE WHEN id > 100 AND type = 0 THEN 10 ELSE type END, + name, + CASE type WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + CASE type WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + CASE color WHEN -1 THEN NULL ELSE color END + FROM _attendanceTypes""") + database.execSQL("""DROP TABLE _attendanceTypes""") + + // Attendance + database.execSQL("""ALTER TABLE attendances RENAME TO _attendances""") + database.execSQL("""CREATE TABLE `attendances` ( + `attendanceLessonTopic` TEXT, + `attendanceLessonNumber` INTEGER, + `profileId` INTEGER NOT NULL, + `attendanceId` INTEGER NOT NULL, + `attendanceBaseType` INTEGER NOT NULL, + `attendanceTypeName` TEXT NOT NULL, + `attendanceTypeShort` TEXT NOT NULL, + `attendanceTypeSymbol` TEXT NOT NULL, + `attendanceTypeColor` INTEGER, + `attendanceDate` TEXT NOT NULL, + `attendanceTime` TEXT, + `attendanceSemester` INTEGER NOT NULL, + `teacherId` INTEGER NOT NULL, + `subjectId` INTEGER NOT NULL, + `addedDate` INTEGER NOT NULL, + `keep` INTEGER NOT NULL, + PRIMARY KEY(`profileId`, `attendanceId`) + )""") + database.execSQL("""DROP INDEX IF EXISTS index_attendances_profileId""") + database.execSQL("""CREATE INDEX `index_attendances_profileId` ON `attendances` (`profileId`)""") + database.execSQL("""REPLACE INTO attendances ( + attendanceLessonTopic, attendanceLessonNumber, profileId, attendanceId, + attendanceBaseType, + attendanceTypeName, + attendanceTypeShort, + attendanceTypeSymbol, + attendanceTypeColor, attendanceDate, attendanceTime, attendanceSemester, teacherId, subjectId, addedDate, keep + ) SELECT + attendanceLessonTopic, NULL, profileId, attendanceId, + attendanceType, + CASE attendanceType WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + CASE attendanceType WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + CASE attendanceType WHEN 0 THEN "ob" WHEN 1 THEN "nb" WHEN 2 THEN "u" WHEN 3 THEN "zw" WHEN 4 THEN "sp" WHEN 5 THEN "su" WHEN 6 THEN "w" ELSE "?" END, + NULL, attendanceLessonDate, attendanceStartTime, attendanceSemester, teacherId, subjectId, 0, 1 + FROM _attendances""") + database.execSQL("""DROP TABLE _attendances""") + + // Events + database.execSQL("""ALTER TABLE events ADD COLUMN addedDate INTEGER NOT NULL DEFAULT 0""") + + // Grades + database.execSQL("""ALTER TABLE grades ADD COLUMN addedDate INTEGER NOT NULL DEFAULT 0""") + database.execSQL("""ALTER TABLE grades ADD COLUMN keep INTEGER NOT NULL DEFAULT 1""") + + // Lucky Numbers + database.execSQL("""ALTER TABLE luckyNumbers ADD COLUMN keep INTEGER NOT NULL DEFAULT 1""") + + // Messages + database.execSQL("""ALTER TABLE messages ADD COLUMN addedDate INTEGER NOT NULL DEFAULT 0""") + + // Notices + database.execSQL("""ALTER TABLE notices RENAME TO _notices""") + database.execSQL("""CREATE TABLE `notices` ( + `profileId` INTEGER NOT NULL, + `noticeId` INTEGER NOT NULL, + `noticeType` INTEGER NOT NULL, + `noticeSemester` INTEGER NOT NULL, + `noticeText` TEXT NOT NULL, + `noticeCategory` TEXT, + `noticePoints` REAL, + `teacherId` INTEGER NOT NULL, + `addedDate` INTEGER NOT NULL, + `keep` INTEGER NOT NULL, + PRIMARY KEY(`profileId`, `noticeId`) + )""") + database.execSQL("""DROP INDEX IF EXISTS index_notices_profileId""") + database.execSQL("""CREATE INDEX `index_notices_profileId` ON `notices` (`profileId`)""") + database.execSQL("""REPLACE INTO notices ( + profileId, noticeId, noticeType, noticeSemester, + noticeText, + noticeCategory, + noticePoints, + teacherId, addedDate, keep + ) SELECT + profileId, noticeId, noticeType, noticeSemester, + CASE noticeText WHEN NULL THEN "" ELSE noticeText END, + category, + CASE points WHEN 0 THEN NULL ELSE points END, + teacherId, 0, 1 + FROM _notices""") + database.execSQL("""DROP TABLE _notices""") + + // Teacher Absence + database.execSQL("""ALTER TABLE teacherAbsence ADD COLUMN addedDate INTEGER NOT NULL DEFAULT 0""") + database.execSQL("""ALTER TABLE teacherAbsence ADD COLUMN keep INTEGER NOT NULL DEFAULT 1""") + database.execSQL("""CREATE INDEX IF NOT EXISTS `index_teacherAbsence_profileId` ON `teacherAbsence` (`profileId`)""") + + // Timetable + database.execSQL("""ALTER TABLE timetable ADD COLUMN keep INTEGER NOT NULL DEFAULT 1""") + + // Metadata - copy AddedDate to entities + database.execSQL("""UPDATE grades SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = grades.profileId AND metadata.thingId = grades.gradeId AND metadata.thingType = 1), 0)""") + database.execSQL("""UPDATE notices SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = notices.profileId AND metadata.thingId = notices.noticeId AND metadata.thingType = 2), 0)""") + database.execSQL("""UPDATE attendances SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = attendances.profileId AND metadata.thingId = attendances.attendanceId AND metadata.thingType = 3), 0)""") + database.execSQL("""UPDATE events SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = events.profileId AND metadata.thingId = events.eventId AND metadata.thingType = 4), 0)""") + database.execSQL("""UPDATE announcements SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = announcements.profileId AND metadata.thingId = announcements.announcementId AND metadata.thingType = 7), 0)""") + database.execSQL("""UPDATE messages SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = messages.profileId AND metadata.thingId = messages.messageId AND metadata.thingType = 8), 0)""") + + // Metadata - drop AddedDate column + database.execSQL("""ALTER TABLE metadata RENAME TO _metadata""") + database.execSQL("""CREATE TABLE metadata (profileId INTEGER NOT NULL, metadataId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, thingType INTEGER NOT NULL, thingId INTEGER NOT NULL, seen INTEGER NOT NULL, notified INTEGER NOT NULL)""") + database.execSQL("""DROP INDEX IF EXISTS index_metadata_profileId_thingType_thingId""") + database.execSQL("""CREATE UNIQUE INDEX index_metadata_profileId_thingType_thingId ON "metadata" (profileId, thingType, thingId)""") + database.execSQL("""INSERT INTO metadata (profileId, metadataId, thingType, thingId, seen, notified) SELECT profileId, metadataId, thingType, thingId, seen, notified FROM _metadata""") + database.execSQL("""DROP TABLE _metadata""") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt index c394c7cc..2ebb8c66 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt @@ -114,7 +114,8 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: type = json.getLong("type") ?: 0, teacherId = json.getLong("teacherId") ?: -1, subjectId = json.getLong("subjectId") ?: -1, - teamId = team.id + teamId = team.id, + addedDate = json.getLong("addedDate") ?: System.currentTimeMillis() ) if (event.color == -1) event.color = null @@ -128,8 +129,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: if (event.type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, event.id, false, - true, - json.getLong("addedDate") ?: System.currentTimeMillis() + true ) val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT @@ -144,7 +144,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: profileId = profile.id, profileName = profile.name, viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, - addedDate = metadata.addedDate + addedDate = event.addedDate ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong()) notificationList += notification } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/bell/BellSyncTimeChooseDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/bell/BellSyncTimeChooseDialog.kt index 63601c76..4116ab83 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/bell/BellSyncTimeChooseDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/bell/BellSyncTimeChooseDialog.kt @@ -100,7 +100,7 @@ class BellSyncTimeChooseDialog( private fun loadTimeList() { launch { val timeItems = withContext(Dispatchers.Default) { - val lessons = app.db.timetableDao().getForDateNow(App.profileId, today) + val lessons = app.db.timetableDao().getAllForDateNow(App.profileId, today) val items = mutableListOf() lessons.forEach { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt index 03cc4879..c6a7b2f9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt @@ -86,7 +86,7 @@ class DayDialog( ) val lessons = withContext(Dispatchers.Default) { - app.db.timetableDao().getForDateNow(profileId, date) + app.db.timetableDao().getAllForDateNow(profileId, date) }.filter { it.type != Lesson.TYPE_NO_LESSONS } if (lessons.isNotEmpty()) { run { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt index d6d175d9..06b5f1a6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt @@ -484,7 +484,8 @@ class EventManualDialog( type = type ?: Event.TYPE_DEFAULT, teacherId = teacherId ?: -1, subjectId = subjectId ?: -1, - teamId = teamId ?: -1 + teamId = teamId ?: -1, + addedDate = editingEvent?.addedDate ?: System.currentTimeMillis() ).also { it.addedManually = true } @@ -497,8 +498,7 @@ class EventManualDialog( }, eventObject.id, true, - true, - editingEvent?.addedDate ?: System.currentTimeMillis() + true ) launch { @@ -536,10 +536,9 @@ class EventManualDialog( eventObject.apply { sharedBy = profile?.userCode sharedByName = profile?.studentNameLong + addedDate = System.currentTimeMillis() } - metadataObject.addedDate = System.currentTimeMillis() - api.runCatching(activity) { shareEvent(eventObject.withMetadata(metadataObject)) } ?: run { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt index 0e063758..20f3ad21 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt @@ -71,7 +71,7 @@ class GradeDetailsDialog( launch { val historyList = withContext(Dispatchers.Default) { - app.db.gradeDao().getAllWithParentIdNow(App.profileId, grade.id) + app.db.gradeDao().getByParentIdNow(App.profileId, grade.id) } if (historyList.isEmpty()) { b.historyVisible = false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt index d8c11307..443d4734 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt @@ -29,7 +29,7 @@ class TeacherAbsenceAdapter( override fun onBindViewHolder(holder: ViewHolder, position: Int) { val teacherAbsence: TeacherAbsenceFull = teacherAbsenceList[position] - holder.teacherAbsenceTeacher.text = teacherAbsence.teacherFullName + holder.teacherAbsenceTeacher.text = teacherAbsence.teacherName val time = when (teacherAbsence.timeFrom != null && teacherAbsence.timeTo != null) { true -> when (teacherAbsence.dateFrom.compareTo(teacherAbsence.dateTo)) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt index 836197f0..7ffab515 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt @@ -46,7 +46,7 @@ class TeacherAbsenceDialog( b.teacherAbsenceView.setHasFixedSize(true) b.teacherAbsenceView.layoutManager = LinearLayoutManager(activity) - app.db.teacherAbsenceDao().getAllByDateFull(profileId, date).observe(activity as LifecycleOwner, Observer { absenceList -> + app.db.teacherAbsenceDao().getAllByDate(profileId, date).observe(activity as LifecycleOwner, Observer { absenceList -> val adapter = TeacherAbsenceAdapter(activity, date, absenceList) b.teacherAbsenceView.adapter = adapter b.teacherAbsenceView.visibility = View.VISIBLE diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt index 9ee93be9..ec212cea 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt @@ -144,7 +144,7 @@ class AgendaFragment : Fragment(), CoroutineScope { if (!isAdded) return@launch - val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getAllChangesNow(app.profileId) } + val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getChangesNow(app.profileId) } val lessonChangeCounters = mutableListOf() lessons.forEach { lesson -> @@ -180,7 +180,7 @@ class AgendaFragment : Fragment(), CoroutineScope { val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true) if (showTeacherAbsences) { - val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllFullNow(app.profileId) } + val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllNow(app.profileId) } val teacherAbsenceCounters = mutableListOf() teacherAbsenceList.forEach { absence -> diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java index 7f9b600d..ee88da07 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java @@ -59,17 +59,20 @@ public class AnnouncementsAdapter extends RecyclerView.Adapter { - if (announcement.text == null || (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS && !announcement.seen && app.getNetworkUtils().isOnline())) { + if (announcement.getText() == null || (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS && !announcement.getSeen() && app.getNetworkUtils().isOnline())) { EdziennikTask.Companion.announcementGet(App.Companion.getProfileId(), announcement).enqueue(requireContext()); } else { showAnnouncementDetailsDialog(announcement); @@ -162,14 +162,14 @@ public class AnnouncementsFragment extends Fragment { private void showAnnouncementDetailsDialog(AnnouncementFull announcement) { MaterialDialog dialog = new MaterialDialog.Builder(activity) - .title(announcement.subject) + .title(announcement.getSubject()) .customView(R.layout.dialog_announcement, true) .positiveText(R.string.ok) .show(); DialogAnnouncementBinding b = DialogAnnouncementBinding.bind(dialog.getCustomView()); - b.text.setText(announcement.teacherFullName+"\n\n"+ (announcement.startDate != null ? announcement.startDate.getFormattedString() : "-") + (announcement.endDate != null ? " do " + announcement.endDate.getFormattedString() : "")+"\n\n" +announcement.text); - if (!announcement.seen && app.getProfile().getLoginStoreType() != LOGIN_TYPE_LIBRUS) { - announcement.seen = true; + b.text.setText(announcement.getTeacherName() +"\n\n"+ (announcement.getStartDate() != null ? announcement.getStartDate().getFormattedString() : "-") + (announcement.getEndDate() != null ? " do " + announcement.getEndDate().getFormattedString() : "")+"\n\n" +announcement.getText()); + if (!announcement.getSeen() && app.getProfile().getLoginStoreType() != LOGIN_TYPE_LIBRUS) { + announcement.setSeen(true); AsyncTask.execute(() -> App.db.metadataDao().setSeen(App.Companion.getProfileId(), announcement, true)); if (recyclerView.getAdapter() != null) recyclerView.getAdapter().notifyDataSetChanged(); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java index 7b057395..3b7fa0e9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java @@ -51,13 +51,13 @@ public class AttendanceAdapter extends RecyclerView.Adapter { App.db.metadataDao().setSeen(App.Companion.getProfileId(), attendance, true); //Intent i = new Intent("android.intent.action.MAIN").putExtra(MainActivity.ACTION_UPDATE_BADGES, "yes, sure"); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java index 2b5317a6..2b6d3332 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java @@ -217,21 +217,21 @@ public class AttendanceFragment extends Fragment { subjectTotalCount = new LongSparseArray<>(); subjectAbsentCount = new LongSparseArray<>(); for (AttendanceFull attendance: attendanceList) { - if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.type == TYPE_RELEASED) + if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.getBaseType() == TYPE_RELEASED) continue; - int[] subjectTotal = subjectTotalCount.get(attendance.subjectId, new int[3]); - int[] subjectAbsent = subjectAbsentCount.get(attendance.subjectId, new int[3]); + int[] subjectTotal = subjectTotalCount.get(attendance.getSubjectId(), new int[3]); + int[] subjectAbsent = subjectAbsentCount.get(attendance.getSubjectId(), new int[3]); subjectTotal[0]++; - subjectTotal[attendance.semester]++; + subjectTotal[attendance.getSemester()]++; - if (attendance.type == TYPE_ABSENT || attendance.type == TYPE_ABSENT_EXCUSED) { + if (attendance.getBaseType() == TYPE_ABSENT || attendance.getBaseType() == TYPE_ABSENT_EXCUSED) { subjectAbsent[0]++; - subjectAbsent[attendance.semester]++; + subjectAbsent[attendance.getSemester()]++; } - subjectTotalCount.put(attendance.subjectId, subjectTotal); - subjectAbsentCount.put(attendance.subjectId, subjectAbsent); + subjectTotalCount.put(attendance.getSubjectId(), subjectTotal); + subjectAbsentCount.put(attendance.getSubjectId(), subjectAbsent); } } @@ -247,13 +247,13 @@ public class AttendanceFragment extends Fragment { List filteredList = new ArrayList<>(); for (AttendanceFull attendance: attendanceList) { - if (displayMode != MODE_YEAR && attendance.semester != displayMode) + if (displayMode != MODE_YEAR && attendance.getSemester() != displayMode) continue; - if (subjectIdFilter != -1 && attendance.subjectId != subjectIdFilter) + if (subjectIdFilter != -1 && attendance.getSubjectId() != subjectIdFilter) continue; - if (attendance.type != TYPE_PRESENT) + if (attendance.getBaseType() != TYPE_PRESENT) filteredList.add(attendance); - switch (attendance.type) { + switch (attendance.getBaseType()) { case TYPE_PRESENT: presentCount++; break; diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/BehaviourFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/BehaviourFragment.java index 4ec8b4c6..d0b46f4a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/BehaviourFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/BehaviourFragment.java @@ -134,10 +134,10 @@ public class BehaviourFragment extends Fragment { List filteredList = new ArrayList<>(); for (NoticeFull notice: noticeList) { - if (displayMode != MODE_YEAR && notice.semester != displayMode) + if (displayMode != MODE_YEAR && notice.getSemester() != displayMode) continue; filteredList.add(notice); - switch (notice.type) { + switch (notice.getType()) { case Notice.TYPE_POSITIVE: praisesCount++; break; diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt index b1b85780..f495d61e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt @@ -41,10 +41,10 @@ class NoticesAdapter//getting the context and product list with constructor if (app.profile.loginStoreType == LOGIN_TYPE_MOBIDZIENNIK && false) { holder.noticesItemReason.text = bs(null, notice.category, "\n") + notice.text - holder.noticesItemTeacherName.text = app.getString(R.string.notices_points_format, notice.teacherFullName, if (notice.points > 0) "+" + notice.points else notice.points) + holder.noticesItemTeacherName.text = app.getString(R.string.notices_points_format, notice.teacherName, if (notice.points ?: 0f > 0) "+" + notice.points else notice.points) } else { holder.noticesItemReason.text = notice.text - holder.noticesItemTeacherName.text = notice.teacherFullName + holder.noticesItemTeacherName.text = notice.teacherName } holder.noticesItemAddedDate.text = Date.fromMillis(notice.addedDate).formattedString diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/editor/GradesEditorFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/editor/GradesEditorFragment.kt index 3e5978ee..6520ce54 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/editor/GradesEditorFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/editor/GradesEditorFragment.kt @@ -206,7 +206,7 @@ class GradesEditorFragment : Fragment() { gradeCountSemester = 0f averageSemester = 0f - app.db.gradeDao().getAllWhere(App.profileId, "subjectId = " + subject.id).observe(this, Observer { grades -> + app.db.gradeDao().getAllBySubject(App.profileId, subject.id).observe(this, Observer { grades -> for (grade in grades) { if (grade.type == Grade.TYPE_NORMAL) { if (grade.weight < 0) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/viewholder/GradeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/viewholder/GradeViewHolder.kt index e71b7819..2af79ba3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/viewholder/GradeViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/viewholder/GradeViewHolder.kt @@ -52,7 +52,7 @@ class GradeViewHolder( b.gradeWeight.text = weightText b.gradeWeight.isVisible = weightText != null - b.gradeTeacherName.text = grade.teacherFullName + b.gradeTeacherName.text = grade.teacherName b.gradeAddedDate.text = Date.fromMillis(grade.addedDate).let { it.getRelativeString(app, 5) ?: it.formattedStringShort } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CounterActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CounterActivity.kt index 36002444..e337f720 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CounterActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CounterActivity.kt @@ -51,7 +51,7 @@ class CounterActivity : AppCompatActivity(), CoroutineScope { withContext(Dispatchers.Default) { lessonList.apply { clear() - addAll(app.db.timetableDao().getForDateNow(App.profileId, Date.getToday()) + addAll(app.db.timetableDao().getAllForDateNow(App.profileId, Date.getToday()) .filter { it.type != Lesson.TYPE_NO_LESSONS && it.type != Lesson.TYPE_CANCELLED && it.type != Lesson.TYPE_SHIFTED_SOURCE diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt index 0e26eb2c..498beeb6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt @@ -35,6 +35,7 @@ import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel import kotlin.coroutines.CoroutineContext @@ -63,7 +64,7 @@ class HomeGradesCard( } holder.root += b.root - val sevenDaysAgo = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000 + val sevenDaysAgo = Date.getToday().stepForward(0, 0, -7) app.db.gradeDao().getAllFromDate(profile.id, sevenDaysAgo).observe(fragment, Observer { grades.apply { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeLuckyNumberCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeLuckyNumberCard.kt index c5e4aede..31611e48 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeLuckyNumberCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeLuckyNumberCard.kt @@ -60,7 +60,7 @@ class HomeLuckyNumberCard( R.string.home_lucky_number_details b.subText.setText(subTextRes, profile.name ?: "", profile.studentNumber) - app.db.luckyNumberDao().getNearestFuture(profile.id, todayValue).observe(fragment, Observer { luckyNumber -> + app.db.luckyNumberDao().getNearestFuture(profile.id, today).observe(fragment, Observer { luckyNumber -> val isYours = luckyNumber?.number == profile.studentNumber val res: Pair> = when { luckyNumber == null -> R.string.home_lucky_number_no_info to emptyArray() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt index 848d0801..e6464d7d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt @@ -105,7 +105,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { override fun onPageCreated(): Boolean { // observe lesson database - app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons -> + app.db.timetableDao().getAllForDate(App.profileId, date).observe(this, Observer { lessons -> launch { val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllByDateNow(App.profileId, date) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt index 08eda003..e60f24f8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt @@ -87,7 +87,7 @@ class TimeDropdown : TextInputDropDown { ) } } else if (displayMode == DISPLAY_LESSONS && lessonsDate != null) { - val lessons = db.timetableDao().getForDateNow(profileId, lessonsDate!!) + val lessons = db.timetableDao().getAllForDateNow(profileId, lessonsDate!!) if (lessons.isEmpty()) { hours += Item( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/luckynumber/WidgetLuckyNumberProvider.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/luckynumber/WidgetLuckyNumberProvider.kt index 014f5bbe..3dfa12f6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/luckynumber/WidgetLuckyNumberProvider.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/luckynumber/WidgetLuckyNumberProvider.kt @@ -48,7 +48,7 @@ class WidgetLuckyNumberProvider : AppWidgetProvider() { val tomorrowValue = tomorrow.value val profile = app.db.profileDao().getByIdNow(config.profileId) - val luckyNumber = app.db.luckyNumberDao().getNearestFutureNow(config.profileId, todayValue) + val luckyNumber = app.db.luckyNumberDao().getNearestFutureNow(config.profileId, today) val isYours = luckyNumber?.number == profile?.studentNumber var noNumberText = false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt new file mode 100644 index 00000000..953d792c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-28. + */ + +package pl.szczodrzynski.edziennik.utils.managers + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.startCoroutineTimer +import kotlin.coroutines.CoroutineContext + +class AttendanceManager(val app: App) : CoroutineScope { + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + fun getTypeShort(baseType: Int): String { + return when (baseType) { + Attendance.TYPE_PRESENT -> "ob" + Attendance.TYPE_PRESENT_CUSTOM -> "ob?" + Attendance.TYPE_ABSENT -> "nb" + Attendance.TYPE_ABSENT_EXCUSED -> "u" + Attendance.TYPE_RELEASED -> "zw" + Attendance.TYPE_BELATED -> "sp" + Attendance.TYPE_BELATED_EXCUSED -> "su" + Attendance.TYPE_DAY_FREE -> "w" + else -> "?" + } + } + + /* _ _ _____ _____ _ __ _ + | | | |_ _| / ____| (_)/ _(_) + | | | | | | | (___ _ __ ___ ___ _| |_ _ ___ + | | | | | | \___ \| '_ \ / _ \/ __| | _| |/ __| + | |__| |_| |_ ____) | |_) | __/ (__| | | | | (__ + \____/|_____| |_____/| .__/ \___|\___|_|_| |_|\___| + | | + |*/ + fun markAsSeen(attendance: AttendanceFull) { + attendance.seen = true + startCoroutineTimer(500L, 0L) { + app.db.metadataDao().setSeen(attendance.profileId, attendance, true) + } + } +} diff --git a/app/src/main/res/layout/dialog_grade_details.xml b/app/src/main/res/layout/dialog_grade_details.xml index 7a176c02..92781772 100644 --- a/app/src/main/res/layout/dialog_grade_details.xml +++ b/app/src/main/res/layout/dialog_grade_details.xml @@ -125,7 +125,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="0dp" - android:text="@{grade.teacherFullName}" + android:text="@{grade.teacherName}" android:textIsSelectable="true" tools:text="Janósz Kowalski" /> From 90e99e241a051dd64d6c38c9425575f792d773e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 29 Apr 2020 15:23:30 +0200 Subject: [PATCH 02/24] [UI/Home] Show textual period grades in grades card. --- .../edziennik/ui/modules/home/cards/HomeGradesCard.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt index 498beeb6..f1c3373e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeGradesCard.kt @@ -121,7 +121,8 @@ class HomeGradesCard( val gradeName = GradeView( gradeItem.context, grade, - app.gradesManager + app.gradesManager, + periodGradesTextual = true ) totalWidth += gradeName.measuredWidth + 5.dp From 85106a01d73344eb5f256675b1a674ef89766951 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Thu, 30 Apr 2020 21:41:42 +0200 Subject: [PATCH 03/24] [API/Librus] Add online lesson URL to events description. --- .../data/api/edziennik/librus/data/api/LibrusApiEvents.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt index 5999a48e..7a76f88e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt @@ -35,7 +35,7 @@ class LibrusApiEvents(override val data: DataLibrus, events?.forEach { event -> val id = event.getLong("Id") ?: return@forEach val eventDate = Date.fromY_m_d(event.getString("Date")) - val topic = event.getString("Content")?.trim() ?: "" + var topic = event.getString("Content")?.trim() ?: "" val type = event.getJsonObject("Category")?.getLong("Id") ?: -1 val teacherId = event.getJsonObject("CreatedBy")?.getLong("Id") ?: -1 val subjectId = event.getJsonObject("Subject")?.getLong("Id") ?: -1 @@ -46,6 +46,12 @@ class LibrusApiEvents(override val data: DataLibrus, val startTime = lessonRange?.startTime ?: Time.fromH_m(event.getString("TimeFrom")) val addedDate = Date.fromIso(event.getString("AddDate")) + event.getJsonObject("onlineLessonUrl")?.let { onlineLesson -> + val text = onlineLesson.getString("text")?.let { "$it - " } ?: "" + val url = onlineLesson.getString("url") + topic += "\n\n$text$url" + } + val eventObject = Event( profileId = profileId, id = id, From f70a1f573042ecb851ad968e628e1cf6161bff82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 1 May 2020 22:03:13 +0200 Subject: [PATCH 04/24] [DB] Fix homework added date migration. --- .../pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt index 6d505edf..2df57788 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt @@ -161,7 +161,7 @@ class Migration86 : Migration(85, 86) { database.execSQL("""UPDATE grades SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = grades.profileId AND metadata.thingId = grades.gradeId AND metadata.thingType = 1), 0)""") database.execSQL("""UPDATE notices SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = notices.profileId AND metadata.thingId = notices.noticeId AND metadata.thingType = 2), 0)""") database.execSQL("""UPDATE attendances SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = attendances.profileId AND metadata.thingId = attendances.attendanceId AND metadata.thingType = 3), 0)""") - database.execSQL("""UPDATE events SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = events.profileId AND metadata.thingId = events.eventId AND metadata.thingType = 4), 0)""") + database.execSQL("""UPDATE events SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = events.profileId AND metadata.thingId = events.eventId AND (metadata.thingType = 4 OR metadata.thingType = 5)), 0)""") database.execSQL("""UPDATE announcements SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = announcements.profileId AND metadata.thingId = announcements.announcementId AND metadata.thingType = 7), 0)""") database.execSQL("""UPDATE messages SET addedDate = IFNULL((SELECT metadata.addedDate FROM metadata WHERE metadata.profileId = messages.profileId AND metadata.thingId = messages.messageId AND metadata.thingType = 8), 0)""") From d68ab0d01025b7a489bed36380a46425ea969553 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Sat, 2 May 2020 23:55:20 +0200 Subject: [PATCH 05/24] [Fix] Suppress i18n warning in GradesConfigDialog. --- .../edziennik/ui/dialogs/settings/GradesConfigDialog.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt index ad29e8f7..e74f3577 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt @@ -4,6 +4,7 @@ package pl.szczodrzynski.edziennik.ui.dialogs.settings +import android.annotation.SuppressLint import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible @@ -59,6 +60,7 @@ class GradesConfigDialog( dialog.show() }} + @SuppressLint("SetTextI18n") private fun loadConfig() { b.customPlusCheckBox.isChecked = profileConfig.plusValue != null b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked From 5ab5dbe940dc3011deb0adb8446bb13b394df3f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sun, 3 May 2020 13:11:04 +0200 Subject: [PATCH 06/24] [UI] Show nightly version badge in main activity. --- .../pl/szczodrzynski/edziennik/MainActivity.kt | 8 ++++++++ app/src/main/res/layout/activity_szkolny.xml | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 19b2886d..3033f0f0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -18,6 +18,7 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu import androidx.core.graphics.ColorUtils +import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.navigation.NavOptions import com.danimahardhika.cafebar.CafeBar @@ -294,6 +295,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope { mainSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) + if (BuildConfig.VERSION_NAME.contains("nightly")) { + b.nightlyText.isVisible = true + b.nightlyText.text = "Nightly\n"+BuildConfig.VERSION_NAME.substringAfterLast(".") + } + else + b.nightlyText.isVisible = false + navLoading = true b.navView.apply { diff --git a/app/src/main/res/layout/activity_szkolny.xml b/app/src/main/res/layout/activity_szkolny.xml index 7ce06741..bdb2a6e7 100644 --- a/app/src/main/res/layout/activity_szkolny.xml +++ b/app/src/main/res/layout/activity_szkolny.xml @@ -32,6 +32,22 @@ + - \ No newline at end of file + From 6436a1703683952948ac6ef2deb538e6a5174d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 4 May 2020 09:28:39 +0200 Subject: [PATCH 07/24] [API] Fix signatures for nightly versions. --- .../edziennik/data/api/szkolny/interceptor/Signing.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index 65a3be61..a6412ba6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -39,7 +39,7 @@ object Signing { val appPassword by lazy { iLoveApple( "ThisIsOurHardWorkPleaseDoNotCopyOrSteal(c)2019.KubaSz".sha256(), - BuildConfig.VERSION_NAME, + BuildConfig.VERSION_NAME.substringBeforeLast('+'), BuildConfig.VERSION_CODE.toLong() ) } From 9167d53a1a2bd95221f2ce30e69f400718f84c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 4 May 2020 22:47:27 +0200 Subject: [PATCH 08/24] [UI] Add new attendance UI module. --- .../edziennik/config/ProfileConfig.kt | 1 + .../config/ProfileConfigAttendance.kt | 30 +++ .../edziennik/data/db/dao/AttendanceDao.kt | 2 +- .../edziennik/data/db/dao/BaseDao.kt | 4 +- .../edziennik/data/db/entity/Attendance.kt | 23 +- .../settings/AttendanceConfigDialog.kt | 75 +++++++ .../modules/attendance/AttendanceAdapter.java | 135 ------------ .../modules/attendance/AttendanceAdapter.kt | 188 ++++++++++++++++ .../ui/modules/attendance/AttendanceBar.kt | 104 +++++++++ .../modules/attendance/AttendanceFragment.kt | 112 ++++++++++ ...Fragment.java => AttendanceFragment_.java} | 7 +- .../attendance/AttendanceListFragment.kt | 203 ++++++++++++++++++ .../attendance/AttendanceSummaryFragment.kt | 162 ++++++++++++++ .../ui/modules/attendance/AttendanceView.kt | 84 ++++++++ .../attendance/models/AttendanceCount.kt | 20 ++ .../attendance/models/AttendanceDayRange.kt | 23 ++ .../attendance/models/AttendanceEmpty.kt | 7 + .../attendance/models/AttendanceMonth.kt | 25 +++ .../attendance/models/AttendanceSubject.kt | 25 +++ .../viewholder/AttendanceViewHolder.kt | 77 +++++++ .../viewholder/DayRangeViewHolder.kt | 72 +++++++ .../attendance/viewholder/EmptyViewHolder.kt | 29 +++ .../attendance/viewholder/MonthViewHolder.kt | 98 +++++++++ .../viewholder/SubjectViewHolder.kt | 98 +++++++++ .../grades/models/ExpandableItemModel.kt | 2 +- .../modules/settings/SettingsNewFragment.java | 10 + .../utils/managers/AttendanceManager.kt | 25 ++- .../res/layout/attendance_config_dialog.xml | 59 +++++ .../main/res/layout/attendance_fragment.xml | 36 ++++ .../res/layout/attendance_item_attendance.xml | 95 ++++++++ .../res/layout/attendance_item_container.xml | 94 ++++++++ .../layout/attendance_item_container_bar.xml | 128 +++++++++++ .../main/res/layout/attendance_item_empty.xml | 21 ++ .../res/layout/attendance_list_fragment.xml | 47 ++++ .../layout/attendance_summary_fragment.xml | 47 ++++ app/src/main/res/layout/grades_item_empty.xml | 2 +- app/src/main/res/values/strings.xml | 17 +- build.gradle | 2 +- 38 files changed, 2034 insertions(+), 155 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt rename app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/{AttendanceFragment.java => AttendanceFragment_.java} (98%) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt create mode 100644 app/src/main/res/layout/attendance_config_dialog.xml create mode 100644 app/src/main/res/layout/attendance_fragment.xml create mode 100644 app/src/main/res/layout/attendance_item_attendance.xml create mode 100644 app/src/main/res/layout/attendance_item_container.xml create mode 100644 app/src/main/res/layout/attendance_item_container_bar.xml create mode 100644 app/src/main/res/layout/attendance_item_empty.xml create mode 100644 app/src/main/res/layout/attendance_list_fragment.xml create mode 100644 app/src/main/res/layout/attendance_summary_fragment.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt index d956c1ec..c4c65ac3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt @@ -30,6 +30,7 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List { fun getByIdNow(profileId: Int, id: Long) = getOneNow("$QUERY WHERE attendances.profileId = $profileId AND attendanceId = $id") - @Query("UPDATE attendances SET keep = 0 WHERE profileId = :profileId AND attendanceDate > :date") + @Query("UPDATE attendances SET keep = 0 WHERE profileId = :profileId AND attendanceDate >= :date") abstract fun dontKeepAfterDate(profileId: Int, date: Date?) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt index 94677d70..0ae961db 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt @@ -116,6 +116,8 @@ interface BaseDao { if (forceReplace) replaceAll(items) else - upsertAll(items) + upsertAll(items, removeNotKept = false) + + if (removeNotKept) removeNotKept() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt index af165b07..99953548 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt @@ -47,15 +47,18 @@ open class Attendance( var addedDate: Long = System.currentTimeMillis() ) : Keepable() { companion object { - const val TYPE_UNKNOWN = -1 - const val TYPE_PRESENT = 0 - const val TYPE_PRESENT_CUSTOM = 10 // count as presence AND show in the list - const val TYPE_ABSENT = 1 - const val TYPE_ABSENT_EXCUSED = 2 - const val TYPE_RELEASED = 3 - const val TYPE_BELATED = 4 - const val TYPE_BELATED_EXCUSED = 5 - const val TYPE_DAY_FREE = 6 + const val TYPE_UNKNOWN = -1 // #3f51b5 + const val TYPE_PRESENT = 0 // #009688 + const val TYPE_PRESENT_CUSTOM = 10 // count as presence AND show in the list + custom color, fallback: #3f51b5 + const val TYPE_ABSENT = 1 // #ff3d00 + const val TYPE_ABSENT_EXCUSED = 2 // #76ff03 + const val TYPE_RELEASED = 3 // #9e9e9e + const val TYPE_BELATED = 4 // #ffc107 + const val TYPE_BELATED_EXCUSED = 5 // #ffc107 + const val TYPE_DAY_FREE = 6 // #43a047 + + // attendance bar order: + // day_free, present, present_custom, unknown, belated_excused, belated, released, absent_excused, absent, } @ColumnInfo(name = "attendanceLessonTopic") @@ -64,5 +67,5 @@ open class Attendance( var lessonNumber: Int? = null @Ignore - var showAsUnseen = false + var showAsUnseen: Boolean? = null } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt new file mode 100644 index 00000000..e7d66ed5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/AttendanceConfigDialog.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.settings + +import android.annotation.SuppressLint +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.AttendanceConfigDialogBinding +import pl.szczodrzynski.edziennik.onChange + +class AttendanceConfigDialog( + val activity: AppCompatActivity, + private val reloadOnDismiss: Boolean = true, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) { + companion object { + const val TAG = "GradesConfigDialog" + } + + private val app by lazy { activity.application as App } + private val profileConfig by lazy { app.config.getFor(app.profileId).attendance } + + private lateinit var b: AttendanceConfigDialogBinding + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + b = AttendanceConfigDialogBinding.inflate(activity.layoutInflater) + onShowListener?.invoke(TAG) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.menu_attendance_config) + .setView(b.root) + .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + saveConfig() + onDismissListener?.invoke(TAG) + if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget() + } + .create() + initView() + loadConfig() + dialog.show() + }} + + @SuppressLint("SetTextI18n") + private fun loadConfig() { + b.useSymbols.isChecked = profileConfig.useSymbols + b.groupConsecutiveDays.isChecked = profileConfig.groupConsecutiveDays + b.showPresenceInMonth.isChecked = profileConfig.showPresenceInMonth + } + + private fun saveConfig() { + // nothing to do here, yet + } + + private fun initView() { + b.useSymbols.onChange { _, isChecked -> + profileConfig.useSymbols = isChecked + } + b.groupConsecutiveDays.onChange { _, isChecked -> + profileConfig.groupConsecutiveDays = isChecked + } + b.showPresenceInMonth.onChange { _, isChecked -> + profileConfig.showPresenceInMonth = isChecked + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java deleted file mode 100644 index 3b7fa0e9..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java +++ /dev/null @@ -1,135 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.attendance; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.os.AsyncTask; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull; - -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT_EXCUSED; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED_EXCUSED; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_DAY_FREE; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_PRESENT; -import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_RELEASED; - -public class AttendanceAdapter extends RecyclerView.Adapter { - private Context context; - public List attendanceList; - - //getting the context and product list with constructor - public AttendanceAdapter(Context mCtx, List noticeList) { - this.context = mCtx; - this.attendanceList = noticeList; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - //inflating and returning our view holder - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.row_attendance_item, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - App app = (App) context.getApplicationContext(); - - AttendanceFull attendance = attendanceList.get(position); - - holder.attendanceLessonTopic.setText(attendance.getLessonTopic()); - holder.attendanceTeacher.setText(attendance.getTeacherName()); - holder.attendanceSubject.setText(attendance.getSubjectLongName()); - holder.attendanceDate.setText(attendance.getDate().getStringDmy()); - holder.attendanceTime.setText(attendance.getStartTime().getStringHM()); - - switch (attendance.getBaseType()) { - case TYPE_DAY_FREE: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff166ee0, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_free_day); - break; - case TYPE_ABSENT: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xfff44336, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_absent); - break; - case TYPE_ABSENT_EXCUSED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffaeea00, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_absent_excused); - break; - case TYPE_BELATED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffca28, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_belated); - break; - case TYPE_BELATED_EXCUSED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff4bb733, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_belated_excused); - break; - case TYPE_RELEASED: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff9e9e9e, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_released); - break; - case TYPE_PRESENT: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffae00, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText(R.string.attendance_present); - break; - default: - holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff03a9f4, PorterDuff.Mode.MULTIPLY)); - holder.attendanceType.setText("?"); - break; - } - holder.attendanceType.setText(attendance.getTypeShort()); - - if (!attendance.getSeen()) { - holder.attendanceLessonTopic.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp)); - holder.attendanceLessonTopic.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - attendance.setSeen(true); - AsyncTask.execute(() -> { - App.db.metadataDao().setSeen(App.Companion.getProfileId(), attendance, true); - //Intent i = new Intent("android.intent.action.MAIN").putExtra(MainActivity.ACTION_UPDATE_BADGES, "yes, sure"); - //context.sendBroadcast(i); - }); - } - else { - holder.attendanceLessonTopic.setBackground(null); - } - } - - @Override - public int getItemCount() { - return attendanceList.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - TextView attendanceType; - TextView attendanceLessonTopic; - TextView attendanceSubject; - TextView attendanceTeacher; - TextView attendanceDate; - TextView attendanceTime; - - ViewHolder(View itemView) { - super(itemView); - attendanceType = itemView.findViewById(R.id.attendanceType); - attendanceLessonTopic = itemView.findViewById(R.id.attendanceLessonTopic); - attendanceSubject = itemView.findViewById(R.id.attendanceSubject); - attendanceTeacher = itemView.findViewById(R.id.attendanceTeacher); - attendanceDate = itemView.findViewById(R.id.attendanceDate); - attendanceTime = itemView.findViewById(R.id.attendanceTime); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt new file mode 100644 index 00000000..812e8465 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt @@ -0,0 +1,188 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-29. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.animation.ObjectAnimator +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.startCoroutineTimer +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceEmpty +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject +import pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder.* +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import kotlin.coroutines.CoroutineContext + +class AttendanceAdapter( + val activity: AppCompatActivity, + val type: Int, + var onAttendanceClick: ((item: AttendanceFull) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "AttendanceAdapter" + private const val ITEM_TYPE_ATTENDANCE = 0 + private const val ITEM_TYPE_DAY_RANGE = 1 + private const val ITEM_TYPE_MONTH = 2 + private const val ITEM_TYPE_SUBJECT = 3 + private const val ITEM_TYPE_EMPTY = 4 + const val STATE_CLOSED = 0 + const val STATE_OPENED = 1 + } + + private val app = activity.applicationContext as App + // optional: place the manager here + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + ITEM_TYPE_ATTENDANCE -> AttendanceViewHolder(inflater, parent) + ITEM_TYPE_DAY_RANGE -> DayRangeViewHolder(inflater, parent) + ITEM_TYPE_MONTH -> MonthViewHolder(inflater, parent) + ITEM_TYPE_SUBJECT -> SubjectViewHolder(inflater, parent) + ITEM_TYPE_EMPTY -> EmptyViewHolder(inflater, parent) + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is AttendanceFull -> ITEM_TYPE_ATTENDANCE + is AttendanceDayRange -> ITEM_TYPE_DAY_RANGE + is AttendanceMonth -> ITEM_TYPE_MONTH + is AttendanceSubject -> ITEM_TYPE_SUBJECT + is AttendanceEmpty -> ITEM_TYPE_EMPTY + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + private val onClickListener = View.OnClickListener { view -> + val model = view.getTag(R.string.tag_key_model) + if (model is AttendanceFull) { + onAttendanceClick?.invoke(model) + return@OnClickListener + } + if (model !is ExpandableItemModel<*>) + return@OnClickListener + expandModel(model, view) + } + + fun expandModel(model: ExpandableItemModel<*>?, view: View?, notifyAdapter: Boolean = true) { + model ?: return + val position = items.indexOf(model) + if (position == -1) + return + + view?.findViewById(R.id.dropdownIcon)?.let { dropdownIcon -> + ObjectAnimator.ofFloat( + dropdownIcon, + View.ROTATION, + if (model.state == STATE_CLOSED) 0f else 180f, + if (model.state == STATE_CLOSED) 180f else 0f + ).setDuration(200).start(); + } + + if (model is AttendanceDayRange || model is AttendanceMonth) { + // hide the preview, show summary + val preview = view?.findViewById(R.id.previewContainer) + val summary = view?.findViewById(R.id.summaryContainer) + val percentage = view?.findViewById(R.id.percentage) + preview?.isInvisible = model.state == STATE_CLOSED + summary?.isInvisible = model.state != STATE_CLOSED + percentage?.isVisible = model.state != STATE_CLOSED + } + + if (model.state == STATE_CLOSED) { + + val subItems = when { + model.items.isEmpty() -> listOf(AttendanceEmpty()) + else -> model.items + } + + model.state = STATE_OPENED + items.addAll(position + 1, subItems.filterNotNull()) + if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size) + } + else { + val start = position + 1 + var end: Int = items.size + for (i in start until items.size) { + val model1 = items[i] + val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3 + if (level <= model.level) { + end = i + break + } else { + if (model1 is ExpandableItemModel<*> && model1.state == STATE_OPENED) { + model1.state = STATE_CLOSED + } + } + } + + if (end != -1) { + items.subList(start, end).clear() + if (notifyAdapter) notifyItemRangeRemoved(start, end - start) + } + + model.state = STATE_CLOSED + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val item = items[position] + if (holder !is BindableViewHolder<*, *>) + return + + val viewType = when (holder) { + is AttendanceViewHolder -> ITEM_TYPE_ATTENDANCE + is DayRangeViewHolder -> ITEM_TYPE_DAY_RANGE + is MonthViewHolder -> ITEM_TYPE_MONTH + is SubjectViewHolder -> ITEM_TYPE_SUBJECT + is EmptyViewHolder -> ITEM_TYPE_EMPTY + else -> throw IllegalArgumentException("Incorrect viewType") + } + holder.itemView.setTag(R.string.tag_key_view_type, viewType) + holder.itemView.setTag(R.string.tag_key_position, position) + holder.itemView.setTag(R.string.tag_key_model, item) + + when { + holder is AttendanceViewHolder && item is AttendanceFull -> holder.onBind(activity, app, item, position, this) + holder is DayRangeViewHolder && item is AttendanceDayRange -> holder.onBind(activity, app, item, position, this) + holder is MonthViewHolder && item is AttendanceMonth -> holder.onBind(activity, app, item, position, this) + holder is SubjectViewHolder && item is AttendanceSubject -> holder.onBind(activity, app, item, position, this) + holder is EmptyViewHolder && item is AttendanceEmpty -> holder.onBind(activity, app, item, position, this) + } + + holder.itemView.setOnClickListener(onClickListener) + } + + fun notifyItemChanged(model: Any) { + startCoroutineTimer(1000L, 0L) { + val index = items.indexOf(model) + if (index != -1) + notifyItemChanged(index) + } + } + + override fun getItemCount() = items.size +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt new file mode 100644 index 00000000..5fcdc8e0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-1. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.* +import android.text.TextPaint +import android.util.AttributeSet +import android.view.View +import pl.szczodrzynski.edziennik.dp +import pl.szczodrzynski.edziennik.utils.Colors + +/* https://github.com/JakubekWeg/Mobishit/blob/master/app/src/main/java/jakubweg/mobishit/view/AttendanceBarView.kt */ +class AttendanceBar : View { + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + + private var attendancesList = listOf() + private val mainPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).also { + it.textAlign = Paint.Align.CENTER + } + private var mPath = Path() + private var mCornerRadius: Float = 0.toFloat() + + init { + mCornerRadius = 4.dp.toFloat() + + if (isInEditMode) + setAttendanceData(mapOf( + 0xff43a047.toInt() to 23, + 0xff009688.toInt() to 187, + 0xff3f51b5.toInt() to 46, + 0xff3f51b5.toInt() to 5, + 0xffffc107.toInt() to 5, + 0xff9e9e9e.toInt() to 26, + 0xff76ff03.toInt() to 34, + 0xffff3d00.toInt() to 8 + )) + } + + // color, count + private class AttendanceItem(var color: Int, var count: Int) + + fun setAttendanceData(list: Map) { + attendancesList = list.map { AttendanceItem(it.key, it.value) } + setWillNotDraw(false) + invalidate() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + val r = RectF(0f, 0f, w.toFloat(), h.toFloat()) + mPath = Path().apply { + addRoundRect(r, mCornerRadius, mCornerRadius, Path.Direction.CW) + close() + } + } + + @SuppressLint("DrawAllocation", "CanvasSize") + override fun onDraw(canvas: Canvas?) { + canvas ?: return + + val sum = attendancesList.sumBy { it.count } + if (sum == 0) { + return + } + + canvas.clipPath(mPath) + + val top = paddingTop.toFloat() + val bottom = (height - paddingBottom).toFloat() + var left = paddingLeft.toFloat() + val unitWidth = (width - paddingRight - paddingLeft).toFloat() / sum.toFloat() + + textPaint.color = Color.BLACK + textPaint.textSize = 14.dp.toFloat() + + for (e in attendancesList) { + if (e.count == 0) + continue + + val width = unitWidth * e.count + mainPaint.color = e.color + canvas.drawRect(left, top, left + width, bottom, mainPaint) + + val textBounds = Rect() + textPaint.getTextBounds(e.count.toString(), 0, e.count.toString().length, textBounds) + if (width > textBounds.width() + 8.dp) { + textPaint.color = Colors.legibleTextColor(e.color) + canvas.drawText(e.count.toString(), left + width / 2, bottom - height / 2 + textBounds.height()/2, textPaint) + } + + left += width + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt new file mode 100644 index 00000000..e3d43c7e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.os.AsyncTask +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.databinding.AttendanceFragmentBinding +import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem +import kotlin.coroutines.CoroutineContext + +class AttendanceFragment : Fragment(), CoroutineScope { + companion object { + private const val TAG = "AttendanceFragment" + const val VIEW_DAYS = 0 + const val VIEW_MONTHS = 1 + const val VIEW_SUMMARY = 2 + const val VIEW_LIST = 3 + var pageSelection = 1 + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: AttendanceFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = AttendanceFragmentBinding.inflate(inflater) + b.refreshLayout.setParent(activity.swipeRefreshLayout) + return b.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + + activity.bottomSheet.prependItems( + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_attendance_config) + .withIcon(CommunityMaterial.Icon2.cmd_settings_outline) + .withOnClickListener(View.OnClickListener { + activity.bottomSheet.close() + AttendanceConfigDialog(activity, true, null, null) + }), + BottomSheetSeparatorItem(true), + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_mark_as_read) + .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) + .withOnClickListener(View.OnClickListener { + activity.bottomSheet.close() + AsyncTask.execute { App.db.metadataDao().setAllSeen(App.profileId, Metadata.TYPE_ATTENDANCE, true) } + Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() + }) + ) + activity.gainAttention() + + if (pageSelection == 1) + pageSelection = app.config.forProfile().attendance.attendancePageSelection + + val pagerAdapter = FragmentLazyPagerAdapter( + fragmentManager ?: return, + b.refreshLayout, + listOf( + AttendanceSummaryFragment() to getString(R.string.attendance_tab_summary), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_DAYS) + } to getString(R.string.attendance_tab_days), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_MONTHS) + } to getString(R.string.attendance_tab_months), + + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_LIST) + } to getString(R.string.attendance_tab_list) + ) + ) + b.viewPager.apply { + offscreenPageLimit = 1 + adapter = pagerAdapter + currentItem = pageSelection + addOnPageSelectedListener { + pageSelection = it + app.config.forProfile().attendance.attendancePageSelection = it + } + b.tabLayout.setupWithViewPager(this) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment_.java similarity index 98% rename from app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment_.java index 2b6d3332..c46b3e09 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment_.java @@ -51,7 +51,7 @@ import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_RELEASED import static pl.szczodrzynski.edziennik.data.db.entity.LoginStore.LOGIN_TYPE_VULCAN; import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ATTENDANCE; -public class AttendanceFragment extends Fragment { +public class AttendanceFragment_ extends Fragment { private App app = null; private MainActivity activity = null; @@ -278,11 +278,12 @@ public class AttendanceFragment extends Fragment { b.attendanceView.setVisibility(View.VISIBLE); b.attendanceNoData.setVisibility(View.GONE); if ((adapter = (AttendanceAdapter) b.attendanceView.getAdapter()) != null) { - adapter.attendanceList = filteredList; + //adapter.setItems(filteredList); adapter.notifyDataSetChanged(); } else { - adapter = new AttendanceAdapter(getContext(), filteredList); + //adapter = new AttendanceAdapter(activity, true, null); + //adapter.setItems(filteredList); b.attendanceView.setAdapter(adapter); } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt new file mode 100644 index 00000000..5f5da8ed --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt @@ -0,0 +1,203 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceListFragmentBinding +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.startCoroutineTimer +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment +import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject +import pl.szczodrzynski.edziennik.utils.models.Date +import kotlin.coroutines.CoroutineContext + +class AttendanceListFragment : LazyFragment(), CoroutineScope { + companion object { + private const val TAG = "AttendanceListFragment" + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: AttendanceListFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + private val manager by lazy { app.attendanceManager } + private var viewType = AttendanceFragment.VIEW_DAYS + private var expandSubjectId = 0L + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = AttendanceListFragmentBinding.inflate(inflater) + b.refreshLayout.setParent(activity.swipeRefreshLayout) + return b.root + } + + override fun onPageCreated(): Boolean { startCoroutineTimer(100L) { + if (!isAdded) return@startCoroutineTimer + + viewType = arguments?.getInt("viewType") ?: AttendanceFragment.VIEW_DAYS + expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L + + val adapter = AttendanceAdapter(activity, viewType) + var firstRun = true + + app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceListFragment, Observer { items -> this@AttendanceListFragment.launch { + if (!isAdded) return@launch + + // load & configure the adapter + adapter.items = withContext(Dispatchers.Default) { processAttendance(items) } + if (items.isNotNullNorEmpty() && b.list.adapter == null) { + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addOnScrollListener(onScrollListener) + } + } + adapter.notifyDataSetChanged() + + if (firstRun) { + expandSubject(adapter) + firstRun = false + } + + // show/hide relevant views + b.progressBar.isVisible = false + if (items.isNullOrEmpty()) { + b.list.isVisible = false + b.noData.isVisible = true + } else { + b.list.isVisible = true + b.noData.isVisible = false + } + }}) + + adapter.onAttendanceClick = { + //GradeDetailsDialog(activity, it) + } + }; return true} + + private fun expandSubject(adapter: AttendanceAdapter) { + var expandSubjectModel: GradesSubject? = null + if (expandSubjectId != 0L) { + expandSubjectModel = adapter.items.firstOrNull { it is GradesSubject && it.subjectId == expandSubjectId } as? GradesSubject + adapter.expandModel( + model = expandSubjectModel, + view = null, + notifyAdapter = false + ) + } + + startCoroutineTimer(500L) { + if (expandSubjectModel != null) { + b.list.smoothScrollToPosition( + adapter.items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0) + ) + } + } + } + + @Suppress("SuspendFunctionOnCoroutineScope") + private fun processAttendance(attendance: List): MutableList { + if (attendance.isEmpty()) + return mutableListOf() + + val groupConsecutiveDays = app.config.forProfile().attendance.groupConsecutiveDays + val showPresenceInMonth = app.config.forProfile().attendance.showPresenceInMonth + + if (viewType == AttendanceFragment.VIEW_DAYS) { + val items = attendance + .filter { it.baseType != Attendance.TYPE_PRESENT } + .groupBy { it.date } + .map { AttendanceDayRange( + rangeStart = it.key, + rangeEnd = null, + items = it.value.toMutableList() + ) } + .toMutableList() + + if (groupConsecutiveDays) { + items.sortByDescending { it.rangeStart } + val iterator = items.listIterator() + + var element = iterator.next() + while (iterator.hasNext()) { + var nextElement = iterator.next() + while (Date.diffDays(element.rangeStart, nextElement.rangeStart) <= 1) { + if (element.rangeEnd == null) + element.rangeEnd = element.rangeStart + + element.items.addAll(nextElement.items) + element.rangeStart = nextElement.rangeStart + iterator.remove() + nextElement = iterator.next() + } + element = nextElement + } + } + + return items.toMutableList() + } + else if (viewType == AttendanceFragment.VIEW_MONTHS) { + val items = attendance + .groupBy { it.date.year to it.date.month } + .map { AttendanceMonth( + year = it.key.first, + month = it.key.second, + items = it.value.toMutableList() + ) } + + items.forEach { month -> + month.typeCountMap = month.items + .groupBy { it.baseType } + .map { it.key to it.value.size } + .sortedBy { it.first } + .toMap() + + val totalCount = month.typeCountMap.entries.sumBy { it.value } + val presenceCount = month.typeCountMap.entries.sumBy { + when (it.key) { + Attendance.TYPE_PRESENT, + Attendance.TYPE_PRESENT_CUSTOM, + Attendance.TYPE_BELATED, + Attendance.TYPE_BELATED_EXCUSED, + Attendance.TYPE_RELEASED -> it.value + else -> 0 + } + } + + month.percentage = if (totalCount == 0) + 0f + else + presenceCount.toFloat() / totalCount.toFloat() * 100f + + if (!showPresenceInMonth) + month.items.removeAll { it.baseType == Attendance.TYPE_PRESENT } + } + + return items.toMutableList() + } + return attendance.filter { it.baseType != Attendance.TYPE_PRESENT }.toMutableList() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt new file mode 100644 index 00000000..829f03a0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt @@ -0,0 +1,162 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceListFragmentBinding +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.startCoroutineTimer +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment.Companion.VIEW_SUMMARY +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment +import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject +import kotlin.coroutines.CoroutineContext + +class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { + companion object { + private const val TAG = "AttendanceSummaryFragment" + } + + private lateinit var app: App + private lateinit var activity: MainActivity + private lateinit var b: AttendanceListFragmentBinding + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + private val manager by lazy { app.attendanceManager } + private var expandSubjectId = 0L + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as MainActivity?) ?: return null + context ?: return null + app = activity.application as App + b = AttendanceListFragmentBinding.inflate(inflater) + b.refreshLayout.setParent(activity.swipeRefreshLayout) + return b.root + } + + override fun onPageCreated(): Boolean { startCoroutineTimer(100L) { + if (!isAdded) return@startCoroutineTimer + + expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L + + val adapter = AttendanceAdapter(activity, VIEW_SUMMARY) + var firstRun = true + + app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceSummaryFragment, Observer { items -> this@AttendanceSummaryFragment.launch { + if (!isAdded) return@launch + + // load & configure the adapter + adapter.items = withContext(Dispatchers.Default) { processAttendance(items) } + if (items.isNotNullNorEmpty() && b.list.adapter == null) { + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addOnScrollListener(onScrollListener) + } + } + adapter.notifyDataSetChanged() + + if (firstRun) { + expandSubject(adapter) + firstRun = false + } + + // show/hide relevant views + b.progressBar.isVisible = false + if (items.isNullOrEmpty()) { + b.list.isVisible = false + b.noData.isVisible = true + } else { + b.list.isVisible = true + b.noData.isVisible = false + } + }}) + + adapter.onAttendanceClick = { + //GradeDetailsDialog(activity, it) + } + }; return true} + + private fun expandSubject(adapter: AttendanceAdapter) { + var expandSubjectModel: GradesSubject? = null + if (expandSubjectId != 0L) { + expandSubjectModel = adapter.items.firstOrNull { it is GradesSubject && it.subjectId == expandSubjectId } as? GradesSubject + adapter.expandModel( + model = expandSubjectModel, + view = null, + notifyAdapter = false + ) + } + + startCoroutineTimer(500L) { + if (expandSubjectModel != null) { + b.list.smoothScrollToPosition( + adapter.items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0) + ) + } + } + } + + @Suppress("SuspendFunctionOnCoroutineScope") + private fun processAttendance(attendance: List): MutableList { + if (attendance.isEmpty()) + return mutableListOf() + + val items = attendance + .groupBy { it.subjectId } + .map { AttendanceSubject( + subjectId = it.key, + subjectName = it.value.firstOrNull()?.subjectLongName ?: "", + items = it.value.toMutableList() + ) } + .sortedBy { it.subjectName.toLowerCase() } + + items.forEach { subject -> + subject.typeCountMap = subject.items + .groupBy { it.baseType } + .map { it.key to it.value.size } + .sortedBy { it.first } + .toMap() + + val totalCount = subject.typeCountMap.entries.sumBy { it.value } + val presenceCount = subject.typeCountMap.entries.sumBy { + when (it.key) { + Attendance.TYPE_PRESENT, + Attendance.TYPE_PRESENT_CUSTOM, + Attendance.TYPE_BELATED, + Attendance.TYPE_BELATED_EXCUSED, + Attendance.TYPE_RELEASED -> it.value + else -> 0 + } + } + + subject.percentage = if (totalCount == 0) + 0f + else + presenceCount.toFloat() / totalCount.toFloat() * 100f + + if (!false /* showPresenceInSubject */) + subject.items.removeAll { it.baseType == Attendance.TYPE_PRESENT } + } + + return items.toMutableList() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt new file mode 100644 index 00000000..dfe20eda --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceView.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-29. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import android.annotation.SuppressLint +import android.content.Context +import android.text.TextUtils +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.graphics.ColorUtils +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.dp +import pl.szczodrzynski.edziennik.setTintColor +import pl.szczodrzynski.edziennik.utils.managers.AttendanceManager + +class AttendanceView : AppCompatTextView { + + @JvmOverloads + constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) + + constructor(context: Context, attendance: Attendance, manager: AttendanceManager) : this(context, null) { + setAttendance(attendance, manager, false) + } + + @SuppressLint("RestrictedApi") + fun setAttendance(attendance: Attendance?, manager: AttendanceManager, bigView: Boolean = false) { + if (attendance == null) { + visibility = View.GONE + return + } + visibility = View.VISIBLE + + val attendanceName = if (manager.useSymbols) + attendance.typeSymbol + else + attendance.typeShort + + val attendanceColor = manager.getAttendanceColor(attendance) + + text = when { + attendanceName.isBlank() -> " " + else -> attendanceName + } + + setTextColor(if (ColorUtils.calculateLuminance(attendanceColor) > 0.3) + 0xaa000000.toInt() + else + 0xccffffff.toInt()) + + setBackgroundResource(if (bigView) R.drawable.bg_rounded_8dp else R.drawable.bg_rounded_4dp) + background.setTintColor(attendanceColor) + gravity = Gravity.CENTER + + if (bigView) { + setTextSize(TypedValue.COMPLEX_UNIT_SP, 22f) + setAutoSizeTextTypeUniformWithConfiguration( + 14, + 32, + 1, + TypedValue.COMPLEX_UNIT_SP + ) + setPadding(2.dp, 2.dp, 2.dp, 2.dp) + } + else { + setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) + setPadding(5.dp, 0, 5.dp, 0) + layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + setMargins(0, 0, 5.dp, 0) + } + maxLines = 1 + ellipsize = TextUtils.TruncateAt.END + measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + } +} + diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt new file mode 100644 index 00000000..c2595885 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceCount.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +class AttendanceCount { + var normalSum = 0f + var normalCount = 0 + var normalWeightedSum = 0f + var normalWeightedCount = 0f + + var pointSum = 0f + + var pointAvgSum = 0f + var pointAvgMax = 0f + + var normalAvg: Float? = null + var pointAvgPercent: Float? = null +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt new file mode 100644 index 00000000..e993d785 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceDayRange.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.utils.models.Date + +data class AttendanceDayRange( + var rangeStart: Date, + var rangeEnd: Date?, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt new file mode 100644 index 00000000..46877f63 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceEmpty.kt @@ -0,0 +1,7 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +class AttendanceEmpty diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt new file mode 100644 index 00000000..e1fde3f2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class AttendanceMonth( + val year: Int, + val month: Int, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } + + var typeCountMap: Map = mapOf() + var percentage: Float = 0f +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt new file mode 100644 index 00000000..67332fc9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class AttendanceSubject( + val subjectId: Long, + val subjectName: String, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } + + var typeCountMap: Map = mapOf() + var percentage: Float = 0f +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt new file mode 100644 index 00000000..e67c68b7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.concat +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceItemAttendanceBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class AttendanceViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemAttendanceBinding = AttendanceItemAttendanceBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "AttendanceViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceFull, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + val bullet = " • " + + b.attendanceView.setAttendance(item, manager, bigView = true) + + b.type.text = item.typeName + b.subjectName.text = item.subjectLongName ?: item.lessonTopic + b.dateTime.text = listOf( + item.date.formattedStringShort, + item.startTime?.stringHM, + item.lessonNumber?.let { app.getString(R.string.attendance_lesson_number_format, it) } + ).concat(bullet) + + if (item.showAsUnseen == null) + item.showAsUnseen = !item.seen + + b.unread.isVisible = item.showAsUnseen == true + if (!item.seen) { + manager.markAsSeen(item) + + val container = adapter.items.firstOrNull { + it is ExpandableItemModel<*> && it.items.contains(item) + } as? ExpandableItemModel<*> ?: return + + var hasUnseen = true + if (container is AttendanceDayRange) { + hasUnseen = container.items.any { !it.seen } + container.hasUnseen = hasUnseen + } + if (container is AttendanceMonth) { + hasUnseen = container.items.any { !it.seen } + container.hasUnseen = hasUnseen + } + + // check if the unseen status has changed + if (!hasUnseen) { + adapter.notifyItemChanged(container) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt new file mode 100644 index 00000000..11a5005c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.concat +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class DayRangeViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemContainerBinding = AttendanceItemContainerBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "DayRangeViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceDayRange, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.title.text = listOf( + item.rangeStart.formattedString, + item.rangeEnd?.formattedString + ).concat(" - ") + + b.dropdownIcon.rotation = when (item.state) { + STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.previewContainer.visibility = if (item.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE + b.summaryContainer.visibility = if (item.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE + + b.previewContainer.removeAllViews() + + for (attendance in item.items) { + if (attendance.baseType == Attendance.TYPE_PRESENT_CUSTOM || attendance.baseType == Attendance.TYPE_UNKNOWN) + continue + b.previewContainer.addView(AttendanceView( + contextWrapper, + attendance, + manager + )) + } + if (item.items.isEmpty() || item.items.none { it.baseType != Attendance.TYPE_PRESENT_CUSTOM && it.baseType != Attendance.TYPE_UNKNOWN }) { + b.previewContainer.addView(TextView(contextWrapper).also { + it.setText(R.string.attendance_empty_text) + }) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt new file mode 100644 index 00000000..37fe635c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/EmptyViewHolder.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.AttendanceItemEmptyBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceEmpty +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder + +class EmptyViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemEmptyBinding = AttendanceItemEmptyBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "EmptyViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceEmpty, position: Int, adapter: AttendanceAdapter) { + + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt new file mode 100644 index 00000000..0c46cdab --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-30. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBarBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Date + +class MonthViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemContainerBarBinding = AttendanceItemContainerBarBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "MonthViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceMonth, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.title.text = listOf( + app.resources.getStringArray(R.array.material_calendar_months_array).getOrNull(item.month - 1)?.fixName(), + item.year.toString() + ).concat(" ") + + b.dropdownIcon.rotation = when (item.state) { + STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.attendanceBar.setAttendanceData(item.typeCountMap.mapKeys { manager.getAttendanceColor(it.key) }) + + b.previewContainer.isInvisible = item.state != STATE_CLOSED + b.summaryContainer.isInvisible = item.state == STATE_CLOSED + b.percentage.isVisible = item.state == STATE_CLOSED + + b.previewContainer.removeAllViews() + + val sum = item.typeCountMap.entries.sumBy { it.value }.toFloat() + item.typeCountMap.forEach { (type, count) -> + val layout = LinearLayout(contextWrapper) + val attendance = Attendance( + profileId = 0, + id = 0, + baseType = type, + typeName = "", + typeShort = manager.getTypeShort(type), + typeSymbol = manager.getTypeShort(type), + typeColor = manager.getAttendanceColor(type), + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ) + layout.addView(AttendanceView(contextWrapper, attendance, manager)) + layout.addView(TextView(contextWrapper).also { + it.setText(R.string.attendance_percentage_format, count/sum*100f) + it.setPadding(0, 0, 5.dp, 0) + }) + layout.setPadding(0, 8.dp, 0, 0) + b.previewContainer.addView(layout) + } + + if (item.percentage == 0f) { + b.percentage.isVisible = false + b.percentage.text = null + b.summaryContainer.isVisible = false + b.summaryContainer.text = null + } + else { + b.percentage.setText(R.string.attendance_percentage_format, item.percentage) + b.summaryContainer.setText(R.string.attendance_period_summary_format, item.percentage) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt new file mode 100644 index 00000000..d079a3a1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-4. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBarBinding +import pl.szczodrzynski.edziennik.dp +import pl.szczodrzynski.edziennik.setText +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Date + +class SubjectViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemContainerBarBinding = AttendanceItemContainerBarBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "SubjectViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceSubject, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.title.text = item.subjectName + + b.dropdownIcon.rotation = when (item.state) { + STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.attendanceBar.setAttendanceData(item.typeCountMap.mapKeys { manager.getAttendanceColor(it.key) }) + + b.previewContainer.isInvisible = item.state != STATE_CLOSED + b.summaryContainer.isInvisible = item.state == STATE_CLOSED + b.percentage.isVisible = item.state == STATE_CLOSED + + b.previewContainer.removeAllViews() + + val sum = item.typeCountMap.entries.sumBy { it.value }.toFloat() + item.typeCountMap.forEach { (type, count) -> + val layout = LinearLayout(contextWrapper) + val attendance = Attendance( + profileId = 0, + id = 0, + baseType = type, + typeName = "", + typeShort = manager.getTypeShort(type), + typeSymbol = manager.getTypeShort(type), + typeColor = manager.getAttendanceColor(type), + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ) + layout.addView(AttendanceView(contextWrapper, attendance, manager)) + layout.addView(TextView(contextWrapper).also { + it.setText(R.string.attendance_percentage_format, count/sum*100f) + it.setPadding(0, 0, 5.dp, 0) + }) + layout.setPadding(0, 8.dp, 0, 0) + b.previewContainer.addView(layout) + } + + if (item.percentage == 0f) { + b.percentage.isVisible = false + b.percentage.text = null + b.summaryContainer.isVisible = false + b.summaryContainer.text = null + } + else { + b.percentage.setText(R.string.attendance_percentage_format, item.percentage) + b.summaryContainer.setText(R.string.attendance_period_summary_format, item.percentage) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/models/ExpandableItemModel.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/models/ExpandableItemModel.kt index c52ba310..20cb3ffc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/models/ExpandableItemModel.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/models/ExpandableItemModel.kt @@ -6,7 +6,7 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.models import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter.Companion.STATE_CLOSED -abstract class ExpandableItemModel(val items: MutableList) { +abstract class ExpandableItemModel(open val items: MutableList) { open var level: Int = 3 var state: Int = STATE_CLOSED } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java index 3e3eac0e..45e457fe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java @@ -52,6 +52,7 @@ import pl.szczodrzynski.edziennik.network.NetworkUtils; import pl.szczodrzynski.edziennik.sync.SyncWorker; import pl.szczodrzynski.edziennik.sync.UpdateWorker; import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog; +import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog; import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog; import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog; import pl.szczodrzynski.edziennik.ui.dialogs.sync.NotificationFilterDialog; @@ -885,6 +886,15 @@ public class SettingsNewFragment extends MaterialAboutFragment { .color(IconicsColor.colorInt(iconColor)) ).setOnClickAction(() -> new GradesConfigDialog(activity, false, null, null))); + items.add(new MaterialAboutActionItem( + getString(R.string.menu_attendance_config), + null, + new IconicsDrawable(activity) + .icon(CommunityMaterial.Icon.cmd_calendar_remove_outline) + .size(IconicsSize.dp(iconSizeDp)) + .color(IconicsColor.colorInt(iconColor)) + ).setOnClickAction(() -> new AttendanceConfigDialog(activity, false, null, null))); + registerCardAllowRegistrationItem = new MaterialAboutSwitchItem( getString(R.string.settings_register_allow_registration_text), getString(R.string.settings_register_allow_registration_subtext), diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt index 953d792c..e77e5fbd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt @@ -19,10 +19,13 @@ class AttendanceManager(val app: App) : CoroutineScope { override val coroutineContext: CoroutineContext get() = job + Dispatchers.Default + val useSymbols + get() = app.config.forProfile().attendance.useSymbols + fun getTypeShort(baseType: Int): String { return when (baseType) { Attendance.TYPE_PRESENT -> "ob" - Attendance.TYPE_PRESENT_CUSTOM -> "ob?" + Attendance.TYPE_PRESENT_CUSTOM -> " " Attendance.TYPE_ABSENT -> "nb" Attendance.TYPE_ABSENT_EXCUSED -> "u" Attendance.TYPE_RELEASED -> "zw" @@ -33,6 +36,26 @@ class AttendanceManager(val app: App) : CoroutineScope { } } + fun getAttendanceColor(baseType: Int): Int { + return when (baseType) { + Attendance.TYPE_PRESENT -> 0xff009688.toInt() + Attendance.TYPE_PRESENT_CUSTOM -> 0xff64b5f6.toInt() + Attendance.TYPE_ABSENT -> 0xffff3d00.toInt() + Attendance.TYPE_ABSENT_EXCUSED -> 0xff76ff03.toInt() + Attendance.TYPE_RELEASED -> 0xff9e9e9e.toInt() + Attendance.TYPE_BELATED -> 0xffffc107.toInt() + Attendance.TYPE_BELATED_EXCUSED -> 0xffffc107.toInt() + Attendance.TYPE_DAY_FREE -> 0xff43a047.toInt() + else -> 0xff64b5f6.toInt() + } + } + fun getAttendanceColor(attendance: Attendance): Int { + return (if (useSymbols) attendance.typeColor else null) ?: when (attendance.baseType) { + Attendance.TYPE_PRESENT_CUSTOM -> attendance.typeColor ?: 0xff64b5f6.toInt() + else -> getAttendanceColor(attendance.baseType) + } + } + /* _ _ _____ _____ _ __ _ | | | |_ _| / ____| (_)/ _(_) | | | | | | | (___ _ __ ___ ___ _| |_ _ ___ diff --git a/app/src/main/res/layout/attendance_config_dialog.xml b/app/src/main/res/layout/attendance_config_dialog.xml new file mode 100644 index 00000000..b31edc72 --- /dev/null +++ b/app/src/main/res/layout/attendance_config_dialog.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/attendance_fragment.xml b/app/src/main/res/layout/attendance_fragment.xml new file mode 100644 index 00000000..d83601f0 --- /dev/null +++ b/app/src/main/res/layout/attendance_fragment.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/attendance_item_attendance.xml b/app/src/main/res/layout/attendance_item_attendance.xml new file mode 100644 index 00000000..5362b4a6 --- /dev/null +++ b/app/src/main/res/layout/attendance_item_attendance.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/attendance_item_container.xml b/app/src/main/res/layout/attendance_item_container.xml new file mode 100644 index 00000000..4d33192f --- /dev/null +++ b/app/src/main/res/layout/attendance_item_container.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/attendance_item_container_bar.xml b/app/src/main/res/layout/attendance_item_container_bar.xml new file mode 100644 index 00000000..63ff39ed --- /dev/null +++ b/app/src/main/res/layout/attendance_item_container_bar.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/attendance_item_empty.xml b/app/src/main/res/layout/attendance_item_empty.xml new file mode 100644 index 00000000..2d868178 --- /dev/null +++ b/app/src/main/res/layout/attendance_item_empty.xml @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/attendance_list_fragment.xml b/app/src/main/res/layout/attendance_list_fragment.xml new file mode 100644 index 00000000..0d6e2abe --- /dev/null +++ b/app/src/main/res/layout/attendance_list_fragment.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/attendance_summary_fragment.xml b/app/src/main/res/layout/attendance_summary_fragment.xml new file mode 100644 index 00000000..d3b1404e --- /dev/null +++ b/app/src/main/res/layout/attendance_summary_fragment.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/grades_item_empty.xml b/app/src/main/res/layout/grades_item_empty.xml index 3ae82483..085eed62 100644 --- a/app/src/main/res/layout/grades_item_empty.xml +++ b/app/src/main/res/layout/grades_item_empty.xml @@ -16,6 +16,6 @@ android:textSize="18sp" android:textColor="?android:textColorSecondary" android:textStyle="italic" - android:text="Nie ma ocen w tym semestrze."/> + android:text="@string/grades_empty_text"/> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df773c90..ef8e64bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,7 +69,7 @@ Nieobecności: W tym nieusprawiedliwione: Spóźnienia: - Brak nieobecności. + Nie masz żadnych nieobecności. Obecności: Zwolnienia: Wszystkie przedmioty @@ -1288,4 +1288,19 @@ Połączenie sieciowe Dodaj nowego ucznia Zaloguj konto ucznia/rodzica w aplikacji + lekcja %d + Ustawienia frekwencji + Dni + Miesiące + Podsumowanie + Lista + Obecność w tym okresie: %.2f%% + %.2f%% + Nie ma ocen w tym semestrze. + Nie ma tutaj żadnych nieobecności. + Konfiguracja frekwencji + Używaj symboli i kolorów wg dziennika + Grupuj kolejne dni na liście + Wyświetlaj obecność w widoku miesięcy + Widoczne po rozwinięciu listy diff --git a/build.gradle b/build.gradle index 6143c28c..ffac6f70 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { ] versions = [ - gradleAndroid : "4.0.0-beta03", + gradleAndroid : '4.0.0-beta05', kotlin : ext.kotlin_version, ktx : "1.2.0", From 97412a3736675ff988f69566b29cc947ba5bd02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 5 May 2020 21:46:18 +0200 Subject: [PATCH 09/24] [UI] Add German translation. --- .../modules/settings/SettingsNewFragment.java | 7 +- app/src/main/res/values-de/strings.xml | 1233 +++++++++++++++++ app/src/main/res/values/strings.xml | 35 +- 3 files changed, 1256 insertions(+), 19 deletions(-) create mode 100644 app/src/main/res/values-de/strings.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java index 45e457fe..2c84c878 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java @@ -1145,8 +1145,8 @@ public class SettingsNewFragment extends MaterialAboutFragment { new MaterialDialog.Builder(activity) .title(getString(R.string.settings_about_language_dialog_title)) .content(getString(R.string.settings_about_language_dialog_text)) - .items(getString(R.string.language_system), getString(R.string.language_polish), getString(R.string.language_english)) - .itemsCallbackSingleChoice(app.getConfig().getUi().getLanguage() == null ? 0 : app.getConfig().getUi().getLanguage().equals("pl") ? 1 : 2, (dialog, itemView, which, text) -> { + .items(getString(R.string.language_system), getString(R.string.language_polish), getString(R.string.language_english), getString(R.string.language_german)) + .itemsCallbackSingleChoice(app.getConfig().getUi().getLanguage() == null ? 0 : app.getConfig().getUi().getLanguage().equals("pl") ? 1 : app.getConfig().getUi().getLanguage().equals("en") ? 2 : 3, (dialog, itemView, which, text) -> { switch (which) { case 0: app.getConfig().getUi().setLanguage(null); @@ -1158,6 +1158,9 @@ public class SettingsNewFragment extends MaterialAboutFragment { case 2: app.getConfig().getUi().setLanguage("en"); break; + case 3: + app.getConfig().getUi().setLanguage("de"); + break; } activity.recreate(MainActivity.DRAWER_ITEM_SETTINGS); return true; diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 00000000..da0f36cd --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,1233 @@ + + + + + Semester 1 + Semester 2 + + + Vom E-Klassenbuch + Nach Wert der Note + + + Vom Neuesten + Nach Schulfach-Name + + + Ereignis + Hausaufgabe + + + Januar + Februar + März + April + Mai + Juni + Juli + August + September + Oktober + November + Dezember + + + Januar + Februar + März + April + Mai + Juni + Juli + August + September + Oktober + November + Dezember + + + Abrechnen + (Hinzufügen) + Stundenplanänderungen + Abwesende Lehrer + %s vor + Möglicherweise läuft auf diesem Gerät ein App-Manager, der Probleme mit der automatischen Synchronisierung verursachen könnte.\n\nUm dieses Problem zu vermeiden, schalten Sie die Batterieoptimierung für Szkolny.eu aus.\n\nKlicken Sie OK, um zu den Systemeinstellungen zu gelangen, + Es gab ein Problem mit der Synchronisierung + Konnte Einstellungen nicht öffnen + Sind Sie sicher? + Zusammenfassung - Semester %d + Zusammenfassung - das Ganze Jahr + Abwesenheiten: + Davon unentschuldigt: + Verspätet: + Keine Abwesenheiten + Anwesenheiten: + Freigaben: + Alle Fächer + Zusammenfassung - Laden… + Zurück + Stellen Sie die Sync-Zeit der Glocke ein. Format: ±H:MM:SS + Falsches Format + Pause (%s) + Eine Kalibrierung ist unmöglich, weil es jetzt keine Lektionen gibt. Versuchen Sie es noch einmal, d.h. am Ende der Lektion oder in der Pause. Denken Sie daran, dass Sie dies sogar vor der geplanten Glockenzeit durchführen sollten + Wählen Sie die nächstgelegene Glocke, die synchronisiert werden soll + \n\nAktuelle Kalibrierung: %s + Klicken Sie auf das Glockensymbol, wenn die Glocke läutet. Die Zeit des Zählers wird mit die Glockenzeit kalibriert. \n\nGeplante Glockenzeit ist %s\n\nDie Differenz der Glockenzeit ist im Moment %s + Lektion %s (%s) + Möchten Sie die Kalibrierung zurücksetzen? + Die Glocke ist um %s ungenau + Zeit der zu synchronisierenden Glocke + Mit der Schulglocke kalibrieren + Kalender-App nicht gefunden + Abbrechen + Nach unten bewegen + Nach oben bewegen + Kommende Ereignisse + Gehe zu Noten + Noten - letzte 7 Tage + Keine Noten zu zeigen + Klicken Sie, um Ihre Nummer zu einstellen + Geben Sie Ihre Nummer aus dem Schulregister ein: + Ihre Nummer + Ihre Nummer ist %d. + %d ist heute die Glücknummer. + Gehe zum Stundenplan + Ereignisse: + In einem Moment: + Stundenplan für heute + Lektionsdauer: + %s %s
%s %s]]>
+ %s %s
%s %s]]>
+ Kein Lektionen an diesem Tag + Kein Stundenplan + Jetzt: + Stundenplan für %s + Der Lektion ist vorbei! Das sollten Sie niemals sehen! + Melden Sie einen Fehler! + Kommende Ereignisse + Neueste Noten + Glücksnummer + Stundenplan + Herunterladen + Sie haben Version %s, während %s verfügbar ist + Eine Aktualisierung ist verfügbar + Wählen Sie eine Datei aus + Wählen Sie ein Profil + Schließen + Komponieren + Komponieren + Konfigurieren + In die Zwischenablage kopiert + In die Zwischenablage kopieren + Synchronisieren + Wenn sich die App überhaupt nicht öffnet, können Sie versuchen, die Daten erneut herunterzuladen. Klicken Sie auf die Schaltfläche, dann starten Sie die App neu + Fehlerdetails + Nachricht vom Entwickler + It\'s not a bug. It\'s a feature! + Bitte melden Sie diesen Fehler + Den Fehler melden + Konnte keinen Bericht senden: + Fehlerbericht gesendet + Applikation neustarten + Synchronisieren + E-Klassenbuch:\\nEin unerwarteter Fehler ist aufgetreten.\\nEntschuldigung für das Problem + heute (%s) + morgen (%s) + Sie sollten sich nicht wirklich darum kümmern, was Sie hier sehen + Diese Einstellungen sind für die Entwickler dieser App gedacht, nicht für normale Benutzer.\n\n Sie sind in keiner Weise beschrieben, daher kann ihre Verwendung dazu führen, dass einige Funktionen in der App kaputt gehen, Ihr System beschädigt wird, Daten verloren gehen oder sogar ein Virus auf dem Akku installiert wird.\n\n Seien Sie vorsichtig und verwenden Sie sie mit Bedacht + Developer mode + Prognostizierter Durchschnitt für das %d Semester:\n%#.2f\n + Prognostizierter Jahresdurchschnitt:\n%#.2f\n + Abschlussnoten Durchschnitt für das %d Semester:\n%#.2f\n + Durchschnittliche Jahresabschlussnoten:\n%#.2f\n + Vorgeschlagene Durchschnittsnote für %d Semester:\n%#.2f\n + Vorgeschlagene Jahresdurchschnittsnote:\n%#.2f\n + Notendurchschnitte + Durchschnitts für %d Semester sind nicht verfügbar + Jahresdurchschnitt ist nicht verfügbar + %s - %s (%s Lektionen - %s Stunden %s Minuten) + Keine Ereignisse an diesem tag + Fehlerdetails + Hinzugefügt + Anhänge + Inhalt + Schulfach + Lehrer + Gruppe + Thema + Keine Ereignisse im ausgewählten Zeitraum + Den ganzen Tag + Sie müssen eine Gruppe (Klasse) von Empfängern auswählen, um das Ereignis freizugeben. + Farbe ändern + Wählen Sie eine Lektion aus + -- andere Zeit -- + Datum + Wählen Sie die Uhrzeit + Termin + nächste lektion %s + nächste %s (%s) + --andere Datum-- + heute (%s) + morgen (%s) + Lektion + Wählen Sie eine Lektion aus + Mehr Optionen + Um etwas teilen zu können, müssen Sie der Registrierung zustimmen.\nKeine Sorge, die Registrierung erfolgt automatisch, sodass Sie keine Daten angeben müssen.\n\nKlicken Sie auf Mehr, um mehr über die verarbeiteten Daten zu erfahren . + Sie sind nicht registriert + Kein Lektion an diesem Tag + Kein Schulfach + Kein Lehrer + Kein Gruppe + Es wird kein Zeitplan heruntergeladen… + Dieses Ereignis wurde von jemand anderem geteilt. Sie werden es nur auf Ihrem Gerät entfernen, aber Sie werden es nie wieder erhalten. + Dieses Ereignis wurde von Ihnen geteilt, alse wird es auch auf den Geräten Ihrer Klassenkameraden entfernt. + An Ihre Klasse senden + Die Veranstaltung wird an alle registrierten Personen aus der ausgewählten Gruppe in Ihrer Klasse gesendet. + Die Änderung dieses Ereignis wird an alle aus der ausgewählten Klasse gesendet + Dieses Ereignis wird von den Geräten Ihrer Klassenkameraden entfernt + Dies ist nicht dein Ereignis, daher wird eine Änderungsanfrage an %s geschick + Das können Sie nicht tun, weil Sie die Ereignisfreigabe in den Einstellungen deaktiviert haben. Wollen Sie sie aktivieren? + Ereignisfreigabe ist deaktiviert + Schulfach + Lehrer + Gruppe + Wählen Sie eine Gruppe aus + Lektion/Zeit + Wählen Sie die Zeit + Fügen Sie dem Zeitplan einen Eintrag hinzu + Thema + Geben Sie ein Thema ein + Typ + Wählen Sie einen Typ + Datum hinzugefügt + Kategorie + Klassendurchschnitt + Kommentar + Beschreibung + Notengeschichte + Note ID + Die Note wurde verbessert + (keine Kategorie) + (keine Beschreibung) + semester %d + Lehrer + Wert zum Durchschnitt + Gewicht %.2f + Note Farbe + Nach Wert der Note + Vom E-Klassenbuch + Vom Neuesten + Nach Schulfach-Name + Noten sortieren + Details + Klassenzimmer + Lektions-ID + Lektionsnummer + Lektion verschoben von %s + Lektion auf %s verschoben + Lehrer + Gruppe + Anzeigen + Keine Ereignisse in dieser Lektion + Sie können ein neues Ereignis manuell über die Schaltfläche Hinzufügen hinzufügen + Wählen Sie aus, welche Benachrichtigungen in Ihrem System und der Applikation angezeigt werden sollen + Ausgewählte Benachrichtigungen anzeigen + Profil erfolgreich entfernt + Möchten Sie das ausgewählte Ereignis entfernen? + Manuelles Ereignis + Manuelle Hausaufgaben + Manuelle Synchronisierung + Profil + Große Schrift + Opazität + Thema + nichts tun + getan + Nicht noch einmal fragen + Herunterladen %s (%s)? + Herunterladen… + Datei herunterladen + Die vorhandene Datei kann nicht entfernt werden.\nEs wird eine neue Datei erstellt. + Datei %s existiert bereits.\nWas wollen Sie damit machen? + Neu erstellen + Vorhanden öffnen + Überschreiben + Ein neues Profil hinzufügen + Anwendungsdaten exportieren + Hinzufügen oder Entfernen von Schülerprofilen + Es gibt keine Lektionen mehr für dieses Schulfach. Laden Sie den Stundenplan herunter und versuchen Sie es erneut + Eigenes Schulfach + Abbrechen + Schließen + Fehler melden… + Fehler melden + Synchronisierung abgebrochen + Erste Anmeldung + Benachrichtigungen erstellen + Profil %s synchronisieren… + Dank dieser Funktion kann Szkolny.eu Daten mit einem E-Klassenbuch synchronisieren. Sie können es schließen, weil es im Moment nichts tut. + Synchronisierungsdienst + Kontodaten herunterladen… + Schulankündigungen herunterladen… + Anwesenheit herunterladen… + Anwesenheitskategorien herunterladen… + Schülerverhaltens herunterladen… + Verhaltensnoten herunterladen… + Kalender herunterladen… + Klasseninformationen herunterladen… + Der Liste der Klassenzimmer herunterladen… + Daten herunterladen… + Beschreibende Schulnoten herunterladen… + Wörterbücher herunterladen + Ereigniskategorien herunterladen… + Kalenderereignissen herunterladen… + Prüfungen herunterladen + Kategorie der Schulnoten herunterladen… + Schulnoten Kommetare herunterladen… + Notes herunterladen… + Hausaufgaben herunterladen… + Lektionen herunterladen… + Glücksnummer herunterladen… + Nachrichten herunterladen… + Nachrichten im Posteingang herunterladen… + Nachrichten outbox herunterladen… + Kategorien von Schulnotizen herunterladen… + Notizen herunterladen… + Punktnoten herunterladen… + Vorschlagene Noten herunterladen… + Schulversammlungen mit den Eltern herunterladen… + Einrichten von Push-Benachrichtigungen… + Schulinfos herunterladen… + Studenteninformationen herunterladen… + Liste der Schulfächer herunterladen… + Lehrer-Abwesenheitstypen herunterladen… + Abwesenheiten von Lehrern herunterladen… + Lehrerliste herunterladen… + Klassengruppen herunterladen… + Stundenplan herunterladen… + Geräteinformationen herunterladen… + bei Edudziennik einloggen… + bei iDziennik einloggen… + bei iDziennik einloggen… + Einloggen in die API… + Einloggen bei Librus Nachrichten… + Einloggen bei Librus Portal… + Einloggen bei Librus Synergia… + Einloggen bei MobiDziennik… + Einloggen bei MobiDziennik… + Einloggen bei MobiDziennik API… + Einloggen bei Template API… + Einloggen bei Template WEB… + Einloggen bei Vulcan + Gemeinsame Ereignisse synchronisieren… + Erstellen von Benachrichtigungen… + Fehler + Fehlerdetails + Falsches Modul angegeben: %d Melden Sie es dem Anwendungsentwickler + Ihr iDziennik-Passwort ist abgelaufen. Sie müssen es in der Desktop-Version der Website ändern + Die Schulfach-ID wurde nicht angegeben + Ein Fehler ist aufgetreten! + Ein Fehler ist aufgetreten: %s + Konnte die Lektion nicht lesen + Klassenbuch-ID wurde nicht gefunden + Der Schüler ist in diesem Semester keinem Klassenbuch zugeordnet. + Schuljahr wurde nicht gefunden + Das Editieren der Ereignisse anderer Schuler ist noch nicht implementiert + Student-ID wurde nicht gefunden. Wahrscheinlich wurde die App aktualisiert.\n\nSynchronisieren Sie sie erneut oder versuchen Sie es in 5 Minuten erneut + Kod błędu: %d (in %s) + Hinzugefügt: + den ganzen Tag + Klassenereignis + %1$s von %2$s + %1$s von Ihnen + {cmd-share-variant} %1$s von %2$s + {cmd-share-variant} %1$s von Ihnen + Aufsatz + Prüfung + Schulausflug + Informationen + Hinzufügen %1$s von %2$s%3$s + Hinzufügen %1$s von Ihnen%3$s + Hinzugefügt %1$s%3$s + {cmd-share-variant} %1$s von %2$s%3$s + {cmd-share-variant} %1$s von Ihnen%3$s + Um das Ereignis freigeben zu können, müssen Sie die Serverregistrierungsoption aktivieren. Auf diese Weise können Sie Ereignisse erstellen und empfangen, die in Ihrer Klasse geteilt werden.\n\nWenn Sie auf OK klicken, wird diese automatisch aktiviert.\n\nStellen Sie sicher, dass Sie die Bedingungen lesen und akzeptieren. + Ereignisse teilen + Ereignis entfernen… + Ereignis speichern… + Ereignis teilen… + Ereignis aus dem Rest der Klasse entfernen… + Gemeinsames Ereignis entfernen… + Möchten Sie diese Aufgabe als erledigt markieren?\n\nEs wird nicht auf der Hauptseite und in den aktuellen Hausaufgaben angezeigt. Es wird weiterhin im Zeitplan verfügbar sein. + als erledigt markieren + andere + Projekt + Treffen mit den Eltern + Lesen + Gemeinsames Ereignis entfernen… + {cmd-share-variant} von %s + Ihnen + Ereignisse teilen + Ereignisse teilen… + kurzer Test + Hausaufgaben + Es gibt keine weiteren Ereignisse im Kalender. + Beenden + Häufig gestellte Fragen + Haben Sie eine Antwort auf Ihre Frage gefunden? Wenn nicht, können Sie mich direkt fragen + Haben Sie eine Antwort gefunden? + Wenn Sie eine Frage haben, können Sie zuerst die FAQ-Datenbank öffnen. Wollen Sie diese Website überprüfen? + Eine Frage stellen + Häufig gestellte Fragen - offen + FAQ-Datenbank öffnen + Geschichte des Gesprächs + Senden + Stimmt etwas nicht? Eine Frage, ein Vorschlag? + (keine Beschreibung) + %s (Finale) + %s Finale + %s (vorgeschlagene) + %s - vorgeschlagene + Ausgangspunkte + Ausgangspunkte: Semester %d + von %s + %s (jährlich) + %s - jährlich + %s (jährlich vorgeschlagen) + %s - jährlich vorgeschlagen + %s Punkte, %s Punkte + kein Durchschnitt + Durchschnitt: %s + Punkte: %s + %s Punkte + Summe: %s + %s Punkte + jährlich: %2$s + Zählen Sie den Durchschnitt, wenn alle Gewichte 0 sind + Erlaubt die Berechnung des Durchschnitts von Schulfächern, in denen alle Noten ein Gewicht von 0 haben (werden nicht zum Durchschnitt gezählt).\n\nWenn solche Schulfächer nicht zum Durchschnitt gezählt werden sollen, deaktivieren Sie das Kontrollkästchen neben dieser Einstellung. + Ausgewählte Noten vom Durchschnitt ausschließen + Trennen Sie die Noten mit einem Komma + Geben Sie Noten ein… + Korrigierte Noten aus der Liste ausblenden + Eigener "Minus" -Wert + Eigener "Plus" -Wert + Notenkonfiguration + Fügen Sie eine Note hinzu + Hinzufügen einer Note + Geben Sie das Gewicht der Note ein + Note ändern + Gewicht ändern + Schieben Sie den Grad nach links, um ihn zu ändern oder zu entfernen. Klicken Sie auf die Schaltfläche unten, um eine neue Note hinzuzufügen + Neue Note + Alle wiederherstellen + Semesterdurchschnitt nach Änderungen: + Ihr Semesterdurchschnitt: + Jahresdurchschnitt nach Änderungen: + Ihr Jahresdurchschnitt: + Gewicht %s + anderes Gewicht + (Verbesserung) %s + max %s Punkten + Keine Noten + Semester %d + Semester 1 + Semester 2 + Semester %d: %s + Semester %d: %s%% + Semester %d: %s Punkte + Semester %d + Semester %d + Die aktuellen Einstellungen könnten sich auf den Durchschnitt auswirken. Wenn Sie der Meinung sind, dass dies nicht der Fall ist, klicken Sie auf Konfigurieren. + Es gibt einen benutzerdefinierten \"minus\"/\"plus\"-Wert. Wenn Sie der Meinung sind, dass es nicht korrekt ist, klicken Sie auf Konfigurieren + Notendurchschnitte dienen nur zur Veranschaulichung und können je nach Schuleinstellungen variieren + *erwarteter Durchschnitt + *von den Abschulussnoten\nErwartete: %s + von Abschlussnoten + *von den vorgeschlagenen Noten\nErwartete: %s + *von den vorgeschlagenen Noten + Die vorhergesagte Note für ein bestimmtes Schulfach wird basierend auf dem aktuellen gewichteten Durchschnitt berechnet.\n\nDie Note ist eine Ganzzahl, die der Lehrer basierend auf dem Durchschnitt vergeben würde. Die Zahl wird aufgerundet, wenn der Dezimalteil ,75 überschreitet.\nBeispiel: 3,75 sowie 4,74 ergeben eine gute Note (4).\n\nDer erwartete Durchschnitt aller Schulfächer enthält die auf diese Weise berechneten Abschlussnoten. + Berechnung des Durchschnitts aller Fächer + Zu wenig Daten zur Berechnung des Durchschnitts + Durchschnitt der regulären Noten + Durchschnitt der Punktnoten + Erwartete Noten durchschnitt:\\n%s + Semester 1 + Semester 1 + Notenstatistiken + Jährlich + Wert: %s + Gewicht %s + nicht zum Durchschnitt gezählt + Ende des Jahres": %s + Ende des Jahres": %s%% + Ende des Jahres": %s Punkte + Ganzes Jahr: %s • %s + Hallo leeres Fragment + Hilfe + Die Weiterleitung von Benachrichtigungen ermöglicht es Ihnen, einen PC-Webbrowser zu koppeln, um Benachrichtigungen auf Ihrem Desktop zu empfangen. Dazu gehören neue Noten, Ereignisse, Hausaufgaben usw.\n\nKlicken Sie auf \"Benachrichtigungsweiterleitung\", um zu beginnen + Die Registrierung läuft automatisch beim ersten Login.\n\nEs werden einige Daten an den App-Server gesendet:\n- Ihre Schul-ID und Klassen-ID\n- Ihr Benutzername für die E-Klassenbuch\n- Ihr Vor- und Nachname\n\nDie einzigen Daten, die für andere Personen in Ihrer Klasse sichtbar sind, ist Ihr Name (beim Teilen von Ereignisse). Private Daten (wie Passwort, Noten usw.) werden nirgendwo versendet. Weitere Informationen finden Sie in den Datenschutz-Bestimmungen. + Erneut herunterladen + Ereignis bearbeiten + Gehe zum Fahrplan + Als erledigt markieren + Im Kalender speichern + Karten hinzufügen/entfernen + Wischen Sie nach links, um eine Karte zu entfernen, und halten Sie sie gedrückt, um sich zu bewegen. + %s • Ihre Nummer im Klassenbuch ist %d + %s • Klicken Sie, um Ihre Nummer Klassenbuch einzustellen, + Die Glücksnummer auf %s ist %d. + Keine Information über die Glücksnummer + Heute gibt es keine Glücksnummer. + %d ist die Glücksnummer von heute + %d ist die Glückszahl für morgen + Ihre Nummer wird Glück haben auf %s. + Heute ist Ihre Glücksnummer! + Morgen wird Ihre Glücksnummer sein! + Später: + keine Lektionen + Erste: %s + in einem Moment: %s + Jetzt: %s + %d Lektionen - %s bis %s + Verbleibende Lektionen: %d bis %s + Es gibt keinen Lektionen + In der kommenden Woche gibt es keinen Lektionen + Der Stundenplan wurde nicht heruntergeladen + Herunterladen + Der Stundenplan für die Woche %s ist noch nicht heruntergeladen + Es gibt keinen Stundenplan + Der Stundenplan wurde von Ihrer Schule noch nicht veröffentlicht. Wenden Sie sich für weitere Informationen an Ihren Klassenlehrer + Heute + Morgen (%1$s) + Bearbeiten + Es gibt keine Hausaufgaben. + Aktuell + Vergangenheit + Ich stimme zu + Ich stimme nicht zu + in %s + Falsches Format + Systemsprache verwenden + Neue Lektion + Pause + Lektion abgebrochen + Ersatz + Verschobene Lektion + Stundenplanänderung + Heute gibt es keine Lektionen mehr! + Laden… + Dieses Konto hat keine Studenten + Sie können nicht anmelden, weil diesem Konto kein Student zugeordnet ist.\n\nEinem Studenten auf der E-Klassenbuch-Seite einen Studenten zuordnen oder mit einem Konto anmelden, dem bereits ein Student zugeordnet ist + Registrierung erlauben + Einloggen + Möchten Sie die Anmeldung bei der App wirklich abbrechen? Angezeigte Profile werden nicht gespeichert. + Verwenden Sie die Kontodaten, mit denen Sie sich bei der Desktop-Version von Edudziennik einlogen + Einloggen - Edudziennik + Fehler: %s + Konto ist nicht aktiviert + Token ist abgelaufen. Erzeugen Sie ein neues Token + Falscher Servername + Falscher Code + Falscher Code oder PIN + Falsche E-Mail + Falsche login + Falsche login oder falsches Passwort + Falsche PIN + Falsche PIN. Verbleibende Versuche: %s + Falscher Schulname + Falsches Schulsymbol + Falsches Token + Falsches Token oder falsche PIN + Falscher Benutzername + Geben Sie Server-Adresse ein + keine Argumente angegeben + Geben Sie den Code ein + Geben Sie die E-Mail-Adresse ein + Geben Sie den Login ein + Geben Sie das Passwort ein + Geben Sie die PIN ein + Geben Sie den Namen der Schule ein + Geben Sie das Schulsymbol ein + Geben Sie den Token ein + Geben Sie den Benutzernamen ein + Altes Passwort verwendet + Anmeldung fehlgeschlagen + Falsche Login/falsches Passwort.\n\nWenn Sie glauben, dass dies ein Fehler ist, melden Sie ihn bitte. + Sie können jetzt die App verwenden. In den Einstellungen können Sie es an Ihre Anforderungen anpassen. + Ein weiteres Profil wurde hinzugefügt. Sie können zwischen Profilen wechseln, indem Sie in der App auf die Kopfzeile des Slideout-Menüs klicken. + Alles ist fertig! + Woher erhalten Sie diese Daten? + Name des Schulservers + Adresse + E-mail + Login + Login / e-mail + Passwort + PIN + Name der Schule + Symbol + Token + Benutzername + Verwenden Sie die Daten, mit denen Sie sich bei der Desktop-Version von iDziennik einloggen + Hilfe - iDziennik Progman + Melden Sie sich mit den Daten an, die Sie von Ihrer Schule erhalten haben. Bei Problemen verwenden Sie die Schaltfläche unter dem Formular. + Einloggen - iDziennik Progman + Librus - Einloggen + Um die App verwenden zu können, müssen Sie über ein Librus-Konto verfügen. Sie können sie unter portal.librus.pl erstellen. Verwenden Sie die Daten, die Sie an den im Bild markierten Stellen eingeben. + Hilfe - Librus + Melden Sie sich mit dem Token und der PIN an, die Sie in der Rubrik "mobile apps" von Librus Synergia erhalten können + Einloggen - Librus JST + Melden Sie sich mit Ihren Librus-Kontodaten an (zuvor unter Ihrer E-Mail-Adresse erstellt). Melden Sie sich nicht mit den von Ihrer Schule erhaltenen Daten an. Bei Problemen verwenden Sie die Schaltfläche unter dem Formular. + Einloggen - Librus + Ein Migrationsfehler ist aufgetreten. Er wurde bereits gemeldet, was bedeutet, dass ich versuchen werde, ihn zu beheben.\n\nSie können die App weiterhin verwenden. Bei Problemen versuchen Sie, das Profil zu entfernen und neu zu erstellen + Die App hat ein großes Update erhalten. Wenn bei der Operation Probleme auftreten, können Sie mich über die Hilfe und das Feedback im Menü kontaktieren. + Szkolny.eu wurde aktualisiert + Migration… + Adresse: + Login und Passwort: + Verwenden Sie die Daten, mit denen Sie sich in die Desktop-Version von Mobidziennik oder in die offizielle mobile App einloggen + Hilfe - MobiDziennik + Melden Sie sich mit den Daten an, die Sie von Ihrer Schule erhalten haben. Bei Problemen verwenden Sie die Schaltfläche unter dem Formular. + Einloggen - MobiDziennik + Anmelden… + Wählen Sie aus, welches E-Klassenbuch Ihre Schule verwendet. In späteren Phasen können Sie mehrere Konten aus verschiedenen E-Klassenbuchen hinzufügen. + Welches E-Klassenbuch benutzen Sie? + Fügen Sie einen Schüler hinzu + Sie müssen mindestens ein Profil auswählen, um es in der App zu speichern. + Kein Profil ausgewählt + Sie haben sich erfolgreich im E-Klassenbuch angemeldet. Wählen Sie die Schülerprofile aus, die Sie in der App speichern möchten.\n\nSie können über die Schaltfläche unten auf der Seite weitere Konten hinzufügen.\nWenn Sie möchten, können Sie später weitere Konten hinzufügen. + Es ist fast fertig + Sie können beispielsweise die Ereignisfreigabe und -benachrichtigung nicht mehr verwenden. + Möchten Sie die Registrierung wirklich deaktivieren? + Während der Synchronisation ist ein Fehler aufgetreten. Klicken Sie auf die Schaltfläche unten auf der Seite, um dies zu melden.\n\nDie Profile wurden gespeichert, sodass die Anwendung ordnungsgemäß funktionieren sollte . In diesem Fall können Sie die Option Hilfe und Feedback im Menü verwenden.\n. + Synchronisierungsfehler + Profil synchronisieren + Synchronisation… + Geräteregistrierung: + Melden Sie sich bei der Desktop-Version von Vulcan E-Klassenbuch an und wählen Sie die Option \"Dostęp mobilny\". Klicken Sie \"Zarejestruj urządzenie mobilne\". Sie können die Daten eingeben oder den QR-Code scannen und nur die PIN eingeben + Hilfe - Vulkan UONET+ + Erhaltene Daten: + QR-Code scannen + Wählen Sie \"Dostęp mobilny\" auf der E-Klassenbuch-Website, registrieren Sie ein neues Gerät und geben Sie die Daten ein. Bei Problemen verwenden Sie die Schaltfläche unter dem Formular. + Einloggen - Vulkan UONET+ + Hinzufügen… + Ansicht ändern + Generieren Sie einen Blockzeitplan + Notendurchschnitte + Noten Farbe + Alles als gelesen markieren + Erneut laden + Sortieren + F + M + Abbrechen + OK + S + S + D + Heute + D + M + Fügen Sie ein Ereignis hinzu + Eigene Ereigniss oder Hausaufgaben in der Zeitplan speichern + Fügen Sie einen neuen Schüler hinzu + Karten hinzufügen oder entfernen + Zeitplan + Zeitplan-Ansicht ändern + Schwarzes Brett + Anwesenheit + Fehlersuche + Hilfe und FAQ + Den Stundenplan als Bild speichern + Generiert Stundenplan für die ganze Woche + Noten + Berechnungsmethode für den Jahresend-Durchschnitt + Noten-Durchschnitte + Semester- und Jahresenddurchschnitte anzeigen + Noten Farbe + Einstellungen für Noten + Simulator für die Bearbeitung von Noten + Noten sortieren + Hilfe + Startseite + Hausaufgaben + Labor + Profile verwalten + Als gelesen markieren + Alles als gelesen markieren + Nachricht + Schreiben Sie eine Nachricht + Nachrichten + Verhalten + Benachrichtigungen + Löschen + Gelöschte Benachrichtigungen + Setzen Sie Ihre Klassenbuchnummer + Einstellungen + Synchronisieren + Alle synchronisieren + Schablone + Stundenplan + Stundenplan-Editor + Benachrichtigungen weiterleiten + Anhang kann nicht heruntergeladen werden + Beim Herunterladen des Anhangs ist ein interner Fehler aufgetreten. Möglicherweise handelt es sich um eine schwache Internetverbindung + Erneut herunterladen + Sind Sie sicher, dass Sie die Nachricht an ausgewählte Empfänger senden möchten? + Bestätigen Sie das Senden der Nachricht + Fügen Sie einen Anhang hinzu + Nachricht abbrechen + Entwurf speichern + Senden + Dieser Empfänger wurde bereits ausgewählt + Wählen Sie die Empfänger aus + Prüfen Sie, ob die ausgewählten Empfänger korrekt sind + Wählen Sie Empfänger aus der Kategorie %s aus, die Sie der Nachricht hinzufügen möchten + Bitte fügen Sie Empfänger hinzu + Senden + Geben Sie einen Betreff mit mindestens 3 Zeichen ein + Betreff + Geben Sie die Nachricht ein + Schreiben Sie eine Nachricht… + Schreiben Sie eine Nachricht + An + Sind Sie sicher, dass Sie die Nachricht löschen möchten? + Dadurch wird die Nachricht auf die Registerkarte \"Gelöscht\" in der App verschoben. Die Änderungen wirken sich nicht auf die E-Klassenbuch-Nachricht aus (sie wird dort nicht gelöscht). + Fehler beim Herunterladen von Nachrichten + Entwurf + Sie haben keine Nachrichten + Fehler beim herunterladen der Empfängerliste + &nbsp;%s, lies: %s, %s]]> + &nbsp;%s, lies: ja]]> + &nbsp;%s, lies: nein]]> + Antworten + %s um %s + Suche + %d Nachrichten gefunden + Nachricht wurde gesendet + Gelöscht + Posteingang + Gesendet + Mehr + Versuchen Sie, Wi-Fi oder mobile Daten einzuschalten + Sie sind offline + Weiter + Nein + Heute gibt es keine Lektionen! + Keine Berechtigungen + Nein, danke + Nicht jetzt + Keine Daten zum Verhalten der Schüler. + Andere: + %s, Punkte: %s + Loben: + Zusammenfassung - Laden… + Zusammenfassung - Semester %d + Zusammenfassung - das ganze Jahr + Warnungen: + Benachrichtigung + Abwesenheit + ntschuldigte Abwesenheit + Schulankündigung von %s: %s + %1$s auf Lektion %2$s auf Tag %3$s + %1$s am Tag %3$s + Verspätung + Entschuldigte Verspätung + Fehler beim Suchen nach Updates + Benachrichtigung über den Fortschritt der Datensynchronisation + Synchronisation + Benachrichtigungen über neue Daten im E-Klassenbuch + Benachrichtigungen + Benachrichtigungen über neue Daten im E-Klassenbuch (kein Ton - während ruhiger Zeiten) + Benachrichtigungen (Nachtruhe) + Benachrichtigungen über neue Versionen der App + App-Aktualisierungen + Benachrichtigungen über ein Problem, das eine Benutzerinteraktion erfordert (z.B. Captcha-Überprüfung). Es wird empfohlen, diese Kategorie eingeschaltet zu lassen + Aktion erforderlich + Klicken, um alle Benachrichtigungen zu sehen + Freier Tag + Updates herunterladen… + %1$s auf %2$s von %3$s + unbekanntes Subjekt + %1$s auf %2$s + Sind Sie sicher, dass Sie diese Einstellungen anwenden möchten?\n\nSie werden Informationen über einige Daten nicht sehen, so dass Sie wichtige Nachrichten oder Noten verpassen könnten.\n\nDie Einstellungen werden auf das aktuell ausgewählte Profil angewendet + Abbrechen + Szkolny.eu: Fehler + Fehler beim herunterladen von Daten für Profil %s + Erneut versuchen + Szkolny.eu: Herunterladen + Daten herunterladen + Aktualisieren + Neue Note (%s) auf %s + Hausaufgaben auf %s für %s + Hausaufgaben für %s + %s auf %s - %s + Heute ist %2$d die Glücksnummer. + Die Glücksnummer für %1$s ist %2$d. + Die Glücksnummer für morgen ist %2$d. + Du hast heute ein Glücksnummer (%2$d) + %1$s Sie werden die Glücksnummer haben! (%2$d) + Morgen ist es Ihre Glücksnummer! (%2$d) + Ungelesene Nachricht von %s: %s + Neue Benachrichtigungen: %d + Kein Update verfügbar + %s von %s auf %s + Neuer Notiz + Neues Lob + Neuer Warnung + Freigabe + %s geteilt %s auf %s - %s + %s geändert %s auf %s - %s + %s entfernt %s auf %s - %s + Neue Lehrerabwesenheit für %s + Anwesenheit + Profilarchivierung + Fehler + Nachricht vom Entwickler + Benachrichtigung + Glücksnummer + Schulankündigung + Neues Ereignis + Neue Note + Neue Hausaufgabe + Neue Nachricht + Neue geteilte Ereignis + Geteilte Hausaufgaben + Neue Abwesenheit von Lehrer + Neuer Notiz + Geteilte Ereignis entfernt + Server-Nachricht + Stundenplanänderung + Lektionsänderung + Update + Szkolny.eu: Update + Klicken Sie zum Herunterladen der Version %s + Update verfügbar + Librus: erfordert eine Lösung für die Captcha-Aufgabe. Klicken Sie hier, um sich weiter im Tagebuch anzumelden. + Das Problem, das die Synchronisierung verhindert, muss vom Benutzer gelöst werden. Klicken Sie für weitere Informationen + Erforderliche Aktion in der App + Keine Benachrichtigungen + OK + Öffnen + Öffnen %s + Keine App, die diesen Dateityp öffnet + Andere + Bitte warten + Datenschutzerklärung + Profil %s wurde archiviert. Die Synchronisierung wurde wegen der Möglichkeit eines Datenverlusts beim nächsten herunterladen deaktiviert.\n\nSie können die in der App in diesem Profil gespeicherten Daten weiterhin anzeigen.\n\nBachten Sie, dass einige Nachrichten möglicherweise nicht korrekt heruntergeladen werden + Profil wird archiviert + Profil %s wurde archiviert, weil das Schuljahr am %s endete. Synchronisirung ist deaktiviert, aber Sie können die Daten dieses Profils trotzdem durchsuchen. + Wenn dies zu lange dauert, versuchen Sie, die App neu zu starten + Wenn dies zu lange dauert, versuchen Sie, die App neu zu starten + App laden… + Einstellungen + Profil entfernen + Sind Sie sicher? + Sie versuchen, das Profil %s zu entfernen. Das bedeutet, dass Sie die Daten dieses Profils (Noten, Stundenpläne, Ereignissen…) aus der App entfernen.\n\nSie können die meisten Daten wiederherstellen, indem Sie sich erneut einloggen.\n\nWollen Sie wirklich Profil %s entfernen? + Einen QR-Code scannen + Niemals + Später + etzt bewerten + Zeigen Sie, dass Ihnen Szkolny.eu gefällt - bewerten Sie die App und machen Sie sie noch besser! + Aktualisieren + Die Registrierung erfolgt automatisch, wenn diese Option aktiviert ist. Sie können Ereignisse erstellen und empfangen, die mit anderen Schülern in Ihrer Klasse geteilt werden. Dank dessen können Sie Elemente, die nicht vom Lehrer gespeichert wurden, zum Klassenbuch hinzufügen.\n\nStellen Sie sicher, dass Sie die Bestimmungen der Datenschutzrichtlinie lesen und die Bestimmungen akzeptieren. + Server-Registrierung + Gemeinsame Ereignisse herunterladen… + Löschen + gelöschtet + Melden + Melden Sie einen Fehler + Zurücksetzen + Speichern + Gespeichert + Keine Schulankündigungen + Nachricht senden… + Änderungsprotokoll + Oder besser nicht klicken… + Klicken Sie hier, um eine unerwartete Ausnahme auszulösen + Treten Sie unserem Discord-Server bei! + Discord Server + Hinweis. Diese Option funktioniert möglicherweise auf einigen Geräten und in einigen Teilen der App nicht. + App-Sprache ändern + Deutsch + Sprache der App + Open-Source-Lizenzen + Datenschutzrichtlinie + E-Klassenbuch + © Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 - April 2020 + Klicken Sie hier, um nach Aktualisierungen zu suchen + Aktualisierung + Version + Mehr + Benachrichtigungsweiterleitung + Bild ändern + Name ändern + Geben Sie einen Namen für das Profil ein + Ändern Sie den Profilnamen + Profil + Deaktivieren Sie bestimmte Arten von Benachrichtigungen + Benachrichtigungen filtern + Benachrichtigungen über dieses Profil anzeigen + Benachrichtigung über neue Daten + Profilbild entfernen + Entfernt das Profil und alle seine Daten + Abmelden / Profil entfernen + Angemeldet als %s + Schließen Sie dieses Profil bei der Synchronisierung ein + Synchronisieren dieses Profil + Einige Funktionen wie Ereignisfreigabe oder Benachrichtigungsweiterleitung können nicht mehr verwendet werden.\n\nDie Funktion kann je nach ausgewähltem Profil aktiviert / deaktiviert werden. + Deaktivieren der Registrierung + Ein Fehler ist aufgetreten, aber Sie sollten sich nicht darum kümmern + Beendet: Erfolg + Benutzerregistrierung aufheben… + Deaktivieren der Registrierung + Durch die Verwendung der Funktion stimme ich den unten in den Einstellungen verfügbaren Datenschutzbestimmungen zu.\n\nDie Funktion kann je nach ausgewähltem Profil aktiviert / deaktiviert werden. + Registrierung erlauben + Nutzen Sie die zusätzlichen Funktionen der App + Registrierung erlauben + Durchschnittswerte beider Semester durchschnittlich + Durchschnitt beider Semester + Endnote von 1.sem + Durchschnitt von 2. sem + Note von 1.sem + Durchschnitt von 2. sem + Durchschnitt von 1. sem + Endnote von 2. sem + Durchschnitt von 1. sem + Note von 2. sem + Der Durchschnitt der Endnoten beider Semester + Durchschnitt der beiden Endnoten + Durchschnitt aller Noten aus beiden Semestern + Durchschnitt aller Noten + Wie berechnet man den Jahresenddurchschnitt?\n\nSie können für jedes Profil eine andere Option festlegen. + Berechnung des Jahresenddurchschnitts + Berechnung des Jahresenddurchschnitts + Aktuelle Zeit: (nicht eingestellt) + Aktuelle Zeit: %s + Passen Sie die Glockensynchronisation an + Beim Warten auf das Ende der Lektion + Zählen Sie die Zeit in Sekunden herunter + Zählen Sie die Note 0 nicht zum Durchschnitt + Dieses E-Register ist noch nicht implementiert + Sie erhalten keine geteilten Ereignisse mehr und können sie nicht mehr teilen. + Sie erhalten geteilte Ereignisse von anderen Personen in Ihrer Klasse + Teile Tests in deiner Klasse + Aktivieren Sie die Ereignisfreigabe + Zeigen Sie die Abwesenheiten von Lehrern im Zeitplan an + Schwarzes Brett + Anwesenheiten + Klasse freie Tage + Klassenzimmern + Ereignisse/Zeitplan + Noten + Hausaufgaben + Glücksnummer + Nachrichten - empfangen + Nachrichten - gesendet + Warnungen/Lob + Punktnoten + Eltern-Lehrer-Treffen + Schulfreie Tagen + Lektionsänderungen und Absagen + Abwesenheiten von Lehrern + Stundenplan + Alle 7 Tage heruntergeladen + Dies kann die Datensynchronisation beschleunigen + Aktivieren / Deaktivieren von Synchronisierungselementen + Passen Sie App-Benachrichtigungen an, z. B. Ton oder Vibration + Einstellungen für Systembenachrichtigungen + Nachtruhe + Startzeit einstellen + Endzeit einstellen + Deaktiviert + Keine Benachrichtigung ertönt von %s bis %s + eine Benachrichtigung ertönt von %s bis %s am nächsten Tag + Nachtruhe + Wann sollen Daten aus dem E-Register synchronisiert werden? + Automatische Synchronisierung + Deaktiviert + Daten alle %s herunterladen + Automatische Synchronisierung + Synchronisation und Benachrichtigungen + Über App-Aktualisierungen benachrichtigen + Benachrichtigungen auf Ihrem PC anzeigen + Benachrichtigungsweiterleitung + Beschränkt die Verwendung von Mobilfunkdaten + Nur über Wi-Fi synchronisieren + App-Hintergrund entfernen + App-Hintergrund festlegen + Bitte, tun Sie das nicht + App-Hintergrund ändern + Hintergrund der Menüüberschrift wiederherstellen + Hintergrund für Menüüberschrift festlegen + Hintergrund der Menüüberschrift ändern + Hinweis. Einige Funktionen sind möglicherweise in einigen E-Registern nicht verfügbar + Mini-Menü Tasten + Passen Sie die Mini-Menüschaltflächen an + Ein Menü auf der linken Seite anzeigen + Mini-Menü anzeigen + Öffnen Sie das Menü mit der Zurück-Taste + Schlittenfahrt im Schnee + Jingle Bells, Jingle Bells + Rosa + System + Thema + Aussehen + Teilen + Teilen über… + Daten Teilen… + Zeitplan + Noten + Hausaufgaben + Nachrichten + Stundenplan + Ein Fehler ist aufgetreten + Nach Datum + Nach Thema + Alle Fächer + Semester 1 + Semester 2 + Ganzes Jahr + Sicher! + Autorisierung + Benachrichtigungen erstellen + Kontoinformationen herunterladen + Kontoliste herunterladen + Zertifikat herunterladen + Tokens aktualisieren + Anmelden… + Datenkonvertierung + Push-Benachrichtigungen konfigurieren + Synchronisieren + Kontoinformationen herunterladen + Schulankündigungen herunterladen + Anwesenheit herunterladen + Anwesenheitskategorien herunterladen + Kategorie der Schulnoten aus Verhaltens herunterladen + Schulnoten aus Verhaltens herunterladen + Zeitplan herunterladen + Klasseninformationen herunterladen + Klassenzimmer herunterladen + Kategorie von Beschreibende Schulnoten herunterladen + Beschreibende Schulnoten herunterladen + Wörterbuch herunterladen + Ereigniskategorien herunterladen + Ereignisse herunterladen + Testen herunterladen + Notenkategorie herunterladen + Notenkommentare herunterladen + Notendetails herunterladen + Noten herunterladen + Hausaufgaben herunterladen + Glücksnummer herunterladen + Nachrichten herunterladen + Empfangene Nachrichten herunterladen + Gesendete Nachrichten herunterladen + Notizendetails herunterladen + Notizen herunterladen + Kategorien von Punktnoten herunterladen + Punktnoten herunterladen + Vorschlagene Noten herunterladen + Treffen mit Eltern herunterladen + Freien Schultagen herunterladen + Schulinformationen herunterladen + Geteilte Ereignisse herunterladen + Schulfächer herunterladen + Abwesenheit von Lehrern herunterladen + Abwesenheitsklassen für Lehrer herunterladen + Klassengruppen herunterladen + Stundenplan herunterladen + Stundenplan änderungen herunterladen + Lehrerliste herunterladen + Daten aktualisieren… + Laden… + synchronisiert + Nicht übereinstimmender Name des Benutzerkontos. Melden Sie einen Fehler. + Anwendungsserverfehler + Das ausgewählte Klassenbuch ist archiviert + Anhang kann nicht heruntergeladen werden. Das Netzwerklaufwerk mit dem Anhang ist nicht verfügbar. + Melden Sie einen Fehler + Synchronisierungsfehler + Ungültiges Gerät + Ungültige Anmeldedaten + Ungültiger Schulname oder verbotene Zeichen + Die Serveradresse ist ungültig. Stellen Sie sicher, dass es korrekt eingegeben wurde und keine Leerzeichen enthält. + Ungültiges Token angegeben. + Das LIBRUS-Konto hat die Verbindung zum Synergia-Konto verloren. Melden Sie sich bei portal.librus.pl oder der offiziellen Librus App an und befolgen Sie die Anweisungen zur Reparatur Ihres Kontos. + Das LIBRUS-Konto wurde nicht aktiviert. Aktivieren Sie das Konto mit der E-Mail, die Sie erhalten haben. + Anmeldefehler + Technische Pause\n\nVersuchen Sie es später erneut + API-Adresse nicht gefunden. Versuchen Sie erneut, sich am Gerät anzumelden. + Keine Internetverbindung\n\nMögliche E-Klassenbuch-Server sind überlastet oder haben eine technische Pause. + Login erfolgreich + Altes Passwort wurde verwendet + Dieses Profil wurde archiviert, daher ist eine Synchronisierung nicht möglich. + Profil nicht gefunden. Versuchen Sie, sich abzumelden und erneut anzumelden. + Diesem Konto sind keine Schüler zugeordnet.\n\nStellen Sie sicher, dass der Schüler Ihrem Konto zugewiesen ist. Überprüfen Sie die Desktop-Version der E-Klassenbuch-Website. + Student %s ist diesem Konto im Klassenbuch nicht zugeordnet (Login: %s).\n\nVersuchen Sie erneut, sich anzumelden, obwohl dies möglicherweise auf eine technische Unterbrechung im E-Klassenbuch hinweist. + Fehler beim Speichern der Daten. Melden Sie den Fehler. + Fehler beim Herstellen einer sicheren Verbindung. + Das Synergia-Konto wurde nicht von einem Elternteil oder Erziehungsberechtigten aktiviert.\n\nLoggen Sie sich in portal.librus.pl ein und befolgen Sie die Anweisungen, um es zu aktivieren. + Auszeit + Unbekannter Fehler + Stundenplan herunterladen + Alles + Ansagen herunterladen… + Anwesenheits herunterladen… + Noten herunterladen… + Hausaufgaben herunterladen… + Empfangene Nachrichten herunterladen… + Gesendete Nachrichten herunterladen + Notizen herunterladen… + Alles synchronisieren… + Wählen Sie aus, welche Elemente synchronisiert werden sollen. + Stundenplan synchronisieren… + Manuelle Synchronisierung + Login Fehler + Die neuesten Daten werden möglicherweise nicht angezeigt + Melden + API-Antwort einschließen (empfohlen) + Fehler melden + Synchronisierungsdienst + Klicken Sie auf das Symbol, um alle Daten als gelesen zu markieren. + Benachrichtigungen + Klicken Sie auf die Menüüberschrift, um das Profil eines Schülers zu ändern oder hinzuzufügen. Hier finden Sie auch eine manuelle Synchronisierung aller Profile. + Profile ändern + Klassenlehrer + Bibliothekar + Andere + Elternteil + Elternrat + Pädagoge / Psychologe + Schulleiter + Schulverwalter + Schulelternrat + Sekretariat + Spezialist + Schüler + Administrator / Superadministrator + Lehrer + Kategorie durchsuchen + Übermorgen + Vorgestern + Bernstein + Schwarz / OLED + Blau + Schokolade + Dunkel + Dunkelblau + Dunkelgrün + Dunkelviolett + Dunkelrot + Indigo + Hell + Hellblau + Hellgrün + Hellviolett + Hellrot + Hellgelb + Violett + rot + Stundenplan anzeigen + Kein Lektionen an diesem Tag: + Freier Tag + Mit Änderungen für diese Woche (%s bis %s) + Druckbar (keine Änderungen, weniger Farben) + Namen des Schülerprofils anzeigen + Mit Änderungen für die nächste Woche (%s bis %s) + Keine Stundenplanänderungen + Druckbar (weniger Farben) + Dies kann einige Sekunden dauern… + Stunden erstellen + Der Umfang des generierten Plans + Für ausgewählte Woche + Profilnamen anzeigen + Namen von Lehrern anzeigen + Der Stundenplan wurde im Verzeichnis Szkolny.eu als Bilddatei gespeichert. \ N \ nSie können ihn sofort öffnen oder freigeben. + Gemacht! + Lektion abgesagt + Ersatz + Ersatz: anstelle von %s + Die Lektion wurde auf einen anderen Tag verschoben + Die Lektion wurde von einem anderen Tag verschoben + Die Lektion wurde von %s %s verschoben + Die Lektion wurde von %s verschoben + "Die Lektion wurde auf %s %s verschoben" + Die Lektion wurde auf %s verschoben + Datum + Wählen Sie das Datum + Wählen Sie das Thema aus + Zeit + Wählen Sie die Zeit + %s (Lektion %d) + Für diesen Tag wurden keine eigenen Lektionen hinzugefügt. + Bei jeder %s Lektion + Beinhaltet einen Stundenplan für jede Woche + Nach Schulfach + Einmal + Wiederholen + (kein Klassenzimmer) + Keinen Lektionen an diesem Tag + Keinen Lektionen an diesem Tag + (kein Name) + Synchronisieren + Der Stundenplan für diese Woche wurde noch nicht synchronisiert + Kein Stundenplan + für Woche %s + Wenden Sie sich an Ihren Tutor, um den Stundenplan zu erhalten. + Der Zeitplan ist nicht öffentlich + Kein Zeitplan + Datum auswählen + Synchronisierung Stundenplan für die ausgewählte Woche… + Heute + Alles synchroniesieren + Debuggen + Feedback + Entwürfe + Empfangene Nachrichten + Gesendeten Nachrichten + Müll + Profilmanager + Wechseln Sie das Semester + Profil ändern… + Heute + Morgen + Synchronisieren… + Unbekannt + Update erforderlich + Sie müssen die App aktualisieren, um sie verwenden zu können. Klicken Sie zum Herunterladen auf OK. + Sie versuchen, eine ältere Version der App auszuführen, deren Daten möglicherweise beschädigt werden. + Gepaarte Browser + Keine Verbindung zum Server. + Gepaart %s + Keine gepaaren Browser. + paaren Sie den Browser + Möchten Sie diesen Browser wirklich löschen? + Das Token sieht nicht gültig aus + Das kannst du nicht machen + trennen + Sie müssen die automatische Registrierung aktivieren, um die Benachrichtigungsweiterleitung verwenden zu können.\n\nWählen Sie in den Anmeldeeinstellungen \"Registrierung zulassen\". + Was willst du machen? + Was ist das? + Was ist neu? + Passen Sie das Widget an + Kombiniert - alle Profile + Wird geladen… + Kein Profil + Glücksnummer + Keine Benachrichtigungen + Benachrichtigungen + Aktualisieren + Synchronisieren + Verwenden Sie eine größere Schriftart + Keinen Lektionen am ausgewählten Tag. + Keine Lektionen für den nächsten 7 Tage. + Der Stundenplan wurde nicht heruntergeladen.\n\nÖffnen Sie die App und synchronisieren Sie sie, um den Stundenplan herunterzuladen. + Das ausgewählte Profil ist nicht vorhanden.\nLöschen Sie das Widget und versuchen Sie es erneut. + Keine Lektionen für den nächsten 7 Tage. + Der Zeitplan wurde nicht heruntergeladen. + Stundenplan + Kombinierter Stundenplan + Ja + gestern + Sie sind offline. Versuchen Sie, Wi-Fi oder mobile Daten zu aktivieren. + Netzwerkverbindung + (Kind) + (Elternteil) +
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ef8e64bc..6f97b5d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,11 +61,24 @@ u sp su + Grupuj kolejne dni na liście + Wyświetlaj obecność w widoku miesięcy + Konfiguracja frekwencji + Używaj symboli i kolorów wg dziennika + Widoczne po rozwinięciu listy + Nie ma tutaj żadnych nieobecności. w + lekcja %d + %.2f%% + Obecność w tym okresie: %.2f%% ob zw Podsumowanie - semestr %d  Podsumowanie - cały rok  + Dni + Lista + Miesiące + Podsumowanie Nieobecności: W tym nieusprawiedliwione: Spóźnienia: @@ -454,6 +467,7 @@ Twoja średnia na koniec roku: waga %s inna waga + Nie ma ocen w tym semestrze. (poprawa) %s max %s pkt Brak ocen w dzienniku. @@ -540,6 +554,7 @@ za %s Nieprawidłowy format English + Deutsch Polski Według systemu Nowa lekcja @@ -672,6 +687,7 @@ Zmień widok Terminarza Tablica ogłoszeń Frekwencja + Ustawienia frekwencji Debugowanie Pomoc i opinie Zapisz plan lekcji jako obraz @@ -907,6 +923,8 @@ Kliknij, aby sprawdzić aktualizacje Aktualizacja Wersja + Zaloguj konto ucznia/rodzica w aplikacji + Dodaj nowego ucznia Więcej Przekazywanie powiadomień Zmień obrazek @@ -1286,21 +1304,4 @@ wczoraj Jesteś offline. Spróbuj włączyć Wi-Fi lub dane komórkowe. Połączenie sieciowe - Dodaj nowego ucznia - Zaloguj konto ucznia/rodzica w aplikacji - lekcja %d - Ustawienia frekwencji - Dni - Miesiące - Podsumowanie - Lista - Obecność w tym okresie: %.2f%% - %.2f%% - Nie ma ocen w tym semestrze. - Nie ma tutaj żadnych nieobecności. - Konfiguracja frekwencji - Używaj symboli i kolorów wg dziennika - Grupuj kolejne dni na liście - Wyświetlaj obecność w widoku miesięcy - Widoczne po rozwinięciu listy From e068f1944f0809755ba8d2ed22878974b428cf37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 5 May 2020 22:57:24 +0200 Subject: [PATCH 10/24] [UI] Add attendance summary page. Disable presence notifications. --- .../data/web/EdudziennikWebAttendance.kt | 4 +- .../data/web/IdziennikWebAttendance.kt | 4 +- .../librus/data/api/LibrusApiAttendances.kt | 4 +- .../data/api/MobidziennikApiAttendance.kt | 4 +- .../data/web/MobidziennikWebAttendance.kt | 4 +- .../vulcan/data/api/VulcanApiAttendance.kt | 4 +- .../edziennik/data/db/entity/Attendance.kt | 5 + .../data/db/entity/AttendanceType.kt | 33 +++- .../edziennik/data/db/full/AttendanceFull.kt | 1 + .../ui/modules/attendance/AttendanceBar.kt | 2 +- .../attendance/AttendanceListFragment.kt | 19 +- .../attendance/AttendanceSummaryFragment.kt | 175 +++++++++++++++-- .../attendance/models/AttendanceMonth.kt | 3 +- .../attendance/models/AttendanceSubject.kt | 3 +- .../attendance/viewholder/MonthViewHolder.kt | 10 +- .../viewholder/SubjectViewHolder.kt | 47 +---- .../utils/managers/AttendanceManager.kt | 7 + .../res/layout/attendance_config_dialog.xml | 3 +- .../attendance_item_container_subject.xml | 80 ++++++++ .../res/layout/attendance_list_fragment.xml | 60 +++--- .../layout/attendance_summary_fragment.xml | 176 +++++++++++++++--- 21 files changed, 500 insertions(+), 148 deletions(-) create mode 100644 app/src/main/res/layout/attendance_item_container_subject.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt index e6f75694..7fe18171 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt @@ -96,8 +96,8 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik, profileId, Metadata.TYPE_ATTENDANCE, id, - profile.empty, - profile.empty + profile.empty || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN, + profile.empty || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt index d67acbdf..e77c9eac 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt @@ -148,8 +148,8 @@ class IdziennikWebAttendance(override val data: DataIdziennik, profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, - profile?.empty ?: false, - profile?.empty ?: false + profile?.empty ?: false || baseType == TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN, + profile?.empty ?: false || baseType == TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt index cf9d7729..7857dc2a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt @@ -77,8 +77,8 @@ class LibrusApiAttendances(override val data: DataLibrus, profileId, Metadata.TYPE_ATTENDANCE, id, - profile?.empty ?: false, - profile?.empty ?: false + profile?.empty ?: false || type?.baseType == Attendance.TYPE_PRESENT_CUSTOM || type?.baseType == Attendance.TYPE_UNKNOWN, + profile?.empty ?: false || type?.baseType == Attendance.TYPE_PRESENT_CUSTOM || type?.baseType == Attendance.TYPE_UNKNOWN )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt index d9d7abb7..473b3691 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt @@ -72,8 +72,8 @@ class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) data.profileId, Metadata.TYPE_ATTENDANCE, id, - data.profile?.empty ?: false, - data.profile?.empty ?: false + data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN, + data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == Attendance.TYPE_UNKNOWN )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt index 57fef0a8..9dd4638d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt @@ -175,8 +175,8 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, data.profileId, Metadata.TYPE_ATTENDANCE, id, - data.profile?.empty ?: false, - data.profile?.empty ?: false + data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN, + data.profile?.empty ?: false || baseType == Attendance.TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt index 2fc816ee..b75da00f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt @@ -73,8 +73,8 @@ class VulcanApiAttendance(override val data: DataVulcan, profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, - profile.empty, - profile.empty + profile.empty || type.baseType == Attendance.TYPE_PRESENT_CUSTOM || type.baseType == Attendance.TYPE_UNKNOWN, + profile.empty || type.baseType == Attendance.TYPE_PRESENT_CUSTOM || type.baseType == Attendance.TYPE_UNKNOWN )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt index 99953548..525c2b39 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt @@ -68,4 +68,9 @@ open class Attendance( @Ignore var showAsUnseen: Boolean? = null + + @delegate:Ignore + val typeObject by lazy { + AttendanceType(profileId, baseType.toLong(), baseType, typeName, typeShort, typeSymbol, typeColor) + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt index f70c7bc6..904c06c6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt @@ -21,4 +21,35 @@ data class AttendanceType ( val typeSymbol: String, /** A color that the e-register would display, null falls back to app's default */ val typeColor: Int? -) +) : Comparable { + + // attendance bar order: + // day_free, present, present_custom, unknown, belated_excused, belated, released, absent_excused, absent, + override fun compareTo(other: AttendanceType): Int { + val type1 = when (baseType) { + Attendance.TYPE_DAY_FREE -> 0 + Attendance.TYPE_PRESENT -> 1 + Attendance.TYPE_PRESENT_CUSTOM -> 2 + Attendance.TYPE_UNKNOWN -> 3 + Attendance.TYPE_BELATED_EXCUSED -> 4 + Attendance.TYPE_BELATED -> 5 + Attendance.TYPE_RELEASED -> 6 + Attendance.TYPE_ABSENT_EXCUSED -> 7 + Attendance.TYPE_ABSENT -> 8 + else -> 9 + } + val type2 = when (other.baseType) { + Attendance.TYPE_DAY_FREE -> 0 + Attendance.TYPE_PRESENT -> 1 + Attendance.TYPE_PRESENT_CUSTOM -> 2 + Attendance.TYPE_UNKNOWN -> 3 + Attendance.TYPE_BELATED_EXCUSED -> 4 + Attendance.TYPE_BELATED -> 5 + Attendance.TYPE_RELEASED -> 6 + Attendance.TYPE_ABSENT_EXCUSED -> 7 + Attendance.TYPE_ABSENT -> 8 + else -> 9 + } + return type1 - type2 + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt index 223ceb38..f235fafd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/AttendanceFull.kt @@ -24,5 +24,6 @@ class AttendanceFull( // metadata var seen = false + get() = field || baseType == TYPE_PRESENT var notified = false } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt index 5fcdc8e0..bfe49a41 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt @@ -93,7 +93,7 @@ class AttendanceBar : View { val textBounds = Rect() textPaint.getTextBounds(e.count.toString(), 0, e.count.toString().length, textBounds) - if (width > textBounds.width() + 8.dp) { + if (width > textBounds.width() + 8.dp && height > textBounds.height() + 2.dp) { textPaint.color = Colors.legibleTextColor(e.color) canvas.drawText(e.count.toString(), left + width / 2, bottom - height / 2 + textBounds.height()/2, textPaint) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt index 5f5da8ed..2f800bb0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt @@ -49,7 +49,6 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { context ?: return null app = activity.application as App b = AttendanceListFragmentBinding.inflate(inflater) - b.refreshLayout.setParent(activity.swipeRefreshLayout) return b.root } @@ -67,7 +66,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { // load & configure the adapter adapter.items = withContext(Dispatchers.Default) { processAttendance(items) } - if (items.isNotNullNorEmpty() && b.list.adapter == null) { + if (adapter.items.isNotNullNorEmpty() && b.list.adapter == null) { b.list.adapter = adapter b.list.apply { setHasFixedSize(true) @@ -76,6 +75,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { } } adapter.notifyDataSetChanged() + setSwipeToRefresh(adapter.items.isNullOrEmpty()) if (firstRun) { expandSubject(adapter) @@ -84,7 +84,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { // show/hide relevant views b.progressBar.isVisible = false - if (items.isNullOrEmpty()) { + if (adapter.items.isNullOrEmpty()) { b.list.isVisible = false b.noData.isVisible = true } else { @@ -141,6 +141,8 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { items.sortByDescending { it.rangeStart } val iterator = items.listIterator() + if (!iterator.hasNext()) + return items.toMutableList() var element = iterator.next() while (iterator.hasNext()) { var nextElement = iterator.next() @@ -170,14 +172,19 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { items.forEach { month -> month.typeCountMap = month.items - .groupBy { it.baseType } + .groupBy { it.typeObject } .map { it.key to it.value.size } .sortedBy { it.first } .toMap() - val totalCount = month.typeCountMap.entries.sumBy { it.value } + val totalCount = month.typeCountMap.entries.sumBy { + when (it.key.baseType) { + Attendance.TYPE_UNKNOWN -> 0 + else -> it.value + } + } val presenceCount = month.typeCountMap.entries.sumBy { - when (it.key) { + when (it.key.baseType) { Attendance.TYPE_PRESENT, Attendance.TYPE_PRESENT_CUSTOM, Attendance.TYPE_BELATED, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt index 829f03a0..2e4378c8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt @@ -4,35 +4,43 @@ package pl.szczodrzynski.edziennik.ui.modules.attendance +import android.graphics.Color import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.AccelerateDecelerateInterpolator +import android.view.animation.Animation +import android.view.animation.Transformation +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.graphics.ColorUtils +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.coroutines.* -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.db.entity.Attendance import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull -import pl.szczodrzynski.edziennik.databinding.AttendanceListFragmentBinding -import pl.szczodrzynski.edziennik.isNotNullNorEmpty -import pl.szczodrzynski.edziennik.startCoroutineTimer +import pl.szczodrzynski.edziennik.databinding.AttendanceSummaryFragmentBinding import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment.Companion.VIEW_SUMMARY import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject +import pl.szczodrzynski.edziennik.utils.models.Date +import java.text.DecimalFormat import kotlin.coroutines.CoroutineContext class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { companion object { private const val TAG = "AttendanceSummaryFragment" + private var periodSelection = 0 } private lateinit var app: App private lateinit var activity: MainActivity - private lateinit var b: AttendanceListFragmentBinding + private lateinit var b: AttendanceSummaryFragmentBinding private val job: Job = Job() override val coroutineContext: CoroutineContext @@ -41,13 +49,13 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { // local/private variables go here private val manager by lazy { app.attendanceManager } private var expandSubjectId = 0L + private var attendance = listOf() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as MainActivity?) ?: return null context ?: return null app = activity.application as App - b = AttendanceListFragmentBinding.inflate(inflater) - b.refreshLayout.setParent(activity.swipeRefreshLayout) + b = AttendanceSummaryFragmentBinding.inflate(inflater) return b.root } @@ -63,16 +71,17 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { if (!isAdded) return@launch // load & configure the adapter - adapter.items = withContext(Dispatchers.Default) { processAttendance(items) } - if (items.isNotNullNorEmpty() && b.list.adapter == null) { + attendance = items + adapter.items = withContext(Dispatchers.Default) { processAttendance() } + if (adapter.items.isNotNullNorEmpty() && b.list.adapter == null) { b.list.adapter = adapter b.list.apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context) - addOnScrollListener(onScrollListener) } } adapter.notifyDataSetChanged() + setSwipeToRefresh(adapter.items.isNullOrEmpty()) if (firstRun) { expandSubject(adapter) @@ -81,10 +90,12 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { // show/hide relevant views b.progressBar.isVisible = false - if (items.isNullOrEmpty()) { + if (adapter.items.isNullOrEmpty()) { + b.statsLayout.isVisible = false b.list.isVisible = false b.noData.isVisible = true } else { + b.statsLayout.isVisible = true b.list.isVisible = true b.noData.isVisible = false } @@ -93,6 +104,36 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { adapter.onAttendanceClick = { //GradeDetailsDialog(activity, it) } + + b.toggleGroup.check(when (periodSelection) { + 0 -> R.id.allYear + 1 -> R.id.semester1 + 2 -> R.id.semester2 + else -> R.id.allYear + }) + b.toggleGroup.addOnButtonCheckedListener { _, checkedId, isChecked -> + if (!isChecked) + return@addOnButtonCheckedListener + periodSelection = when (checkedId) { + R.id.allYear -> 0 + R.id.semester1 -> 1 + R.id.semester2 -> 2 + else -> 0 + } + this@AttendanceSummaryFragment.launch { + adapter.items = withContext(Dispatchers.Default) { processAttendance() } + if (adapter.items.isNullOrEmpty()) { + b.statsLayout.isVisible = false + b.list.isVisible = false + b.noData.isVisible = true + } else { + b.statsLayout.isVisible = true + b.list.isVisible = true + b.noData.isVisible = false + } + adapter.notifyDataSetChanged() + } + } }; return true} private fun expandSubject(adapter: AttendanceAdapter) { @@ -116,7 +157,14 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { } @Suppress("SuspendFunctionOnCoroutineScope") - private fun processAttendance(attendance: List): MutableList { + private fun processAttendance(): MutableList { + val attendance = when (periodSelection) { + 0 -> attendance + 1 -> attendance.filter { it.semester == 1 } + 2 -> attendance.filter { it.semester == 2 } + else -> attendance + } + if (attendance.isEmpty()) return mutableListOf() @@ -129,16 +177,24 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { ) } .sortedBy { it.subjectName.toLowerCase() } + var totalCountSum = 0 + var presenceCountSum = 0 + items.forEach { subject -> subject.typeCountMap = subject.items - .groupBy { it.baseType } + .groupBy { it.typeObject } .map { it.key to it.value.size } .sortedBy { it.first } .toMap() - val totalCount = subject.typeCountMap.entries.sumBy { it.value } + val totalCount = subject.typeCountMap.entries.sumBy { + when (it.key.baseType) { + Attendance.TYPE_UNKNOWN -> 0 + else -> it.value + } + } val presenceCount = subject.typeCountMap.entries.sumBy { - when (it.key) { + when (it.key.baseType) { Attendance.TYPE_PRESENT, Attendance.TYPE_PRESENT_CUSTOM, Attendance.TYPE_BELATED, @@ -147,6 +203,8 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { else -> 0 } } + totalCountSum += totalCount + presenceCountSum += presenceCount subject.percentage = if (totalCount == 0) 0f @@ -157,6 +215,91 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { subject.items.removeAll { it.baseType == Attendance.TYPE_PRESENT } } + val typeCountMap = attendance + .groupBy { it.typeObject } + .map { it.key to it.value.size } + .sortedBy { it.first } + .toMap() + + val percentage = if (totalCountSum == 0) + 0f + else + presenceCountSum.toFloat() / totalCountSum.toFloat() * 100f + + launch { + b.attendanceBar.setAttendanceData(typeCountMap.mapKeys { manager.getAttendanceColor(it.key) }) + b.attendanceBar.isInvisible = typeCountMap.isEmpty() + + b.previewContainer.removeAllViews() + val sum = typeCountMap.entries.sumBy { it.value }.toFloat() + typeCountMap.forEach { (type, count) -> + val layout = LinearLayout(activity) + val attendanceObject = Attendance( + profileId = 0, + id = 0, + baseType = type.baseType, + typeName = "", + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ) + layout.addView(AttendanceView(activity, attendanceObject, manager)) + layout.addView(TextView(activity).also { + it.setText(R.string.attendance_percentage_format, count/sum*100f) + it.setPadding(0, 0, 5.dp, 0) + }) + layout.setPadding(0, 8.dp, 0, 8.dp) + b.previewContainer.addView(layout) + } + + if (percentage == 0f) { + b.percentage.isInvisible = true + b.percentageCircle.isInvisible = true + } + else { + b.percentage.isVisible = true + b.percentageCircle.isVisible = true + b.percentage.setText(R.string.attendance_period_summary_format, percentage) + + val df = DecimalFormat("0.##") + b.percentageCircle.setProgressTextAdapter { value -> + df.format(value) + "%" + } + b.percentageCircle.maxProgress = 100.0 + animatePercentageIndicator(percentage.toDouble()) + } + } + return items.toMutableList() } + + private fun animatePercentageIndicator(targetProgress: Double) { + val startingProgress = b.percentageCircle.progress + val progressChange = targetProgress - startingProgress + + val a: Animation = object : Animation() { + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + val progress = startingProgress + (progressChange * interpolatedTime) + //if (interpolatedTime == 1f) + // progress = startingProgress + progressChange + + val color = ColorUtils.blendARGB(Color.RED, Color.GREEN, progress.toFloat() / 100.0f) + b.percentageCircle.progressColor = color + b.percentageCircle.setProgress(progress, 100.0) + } + + override fun willChangeBounds(): Boolean { + return false + } + } + a.duration = 1300 + a.interpolator = AccelerateDecelerateInterpolator() + b.percentageCircle.startAnimation(a) + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt index e1fde3f2..c6d14914 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceMonth.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.ui.modules.attendance.models import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel @@ -20,6 +21,6 @@ data class AttendanceMonth( var hasUnseen: Boolean = false get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } - var typeCountMap: Map = mapOf() + var typeCountMap: Map = mapOf() var percentage: Float = 0f } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt index 67332fc9..b56cd62a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceSubject.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.ui.modules.attendance.models import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel @@ -20,6 +21,6 @@ data class AttendanceSubject( var hasUnseen: Boolean = false get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } - var typeCountMap: Map = mapOf() + var typeCountMap: Map = mapOf() var percentage: Float = 0f } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt index 0c46cdab..8c010f81 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt @@ -63,11 +63,11 @@ class MonthViewHolder( val attendance = Attendance( profileId = 0, id = 0, - baseType = type, + baseType = type.baseType, typeName = "", - typeShort = manager.getTypeShort(type), - typeSymbol = manager.getTypeShort(type), - typeColor = manager.getAttendanceColor(type), + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, date = Date(0, 0, 0), startTime = null, semester = 0, @@ -80,7 +80,7 @@ class MonthViewHolder( it.setText(R.string.attendance_percentage_format, count/sum*100f) it.setPadding(0, 0, 5.dp, 0) }) - layout.setPadding(0, 8.dp, 0, 0) + layout.setPadding(0, 8.dp, 0, 8.dp) b.previewContainer.addView(layout) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt index d079a3a1..bea33ed4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt @@ -6,31 +6,24 @@ package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder import android.view.LayoutInflater import android.view.ViewGroup -import android.widget.LinearLayout -import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ContextThemeWrapper -import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBarBinding -import pl.szczodrzynski.edziennik.dp +import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerSubjectBinding import pl.szczodrzynski.edziennik.setText import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED -import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder import pl.szczodrzynski.edziennik.utils.Themes -import pl.szczodrzynski.edziennik.utils.models.Date class SubjectViewHolder( inflater: LayoutInflater, parent: ViewGroup, - val b: AttendanceItemContainerBarBinding = AttendanceItemContainerBarBinding.inflate(inflater, parent, false) + val b: AttendanceItemContainerSubjectBinding = AttendanceItemContainerSubjectBinding.inflate(inflater, parent, false) ) : RecyclerView.ViewHolder(b.root), BindableViewHolder { companion object { private const val TAG = "SubjectViewHolder" @@ -51,48 +44,14 @@ class SubjectViewHolder( b.attendanceBar.setAttendanceData(item.typeCountMap.mapKeys { manager.getAttendanceColor(it.key) }) - b.previewContainer.isInvisible = item.state != STATE_CLOSED - b.summaryContainer.isInvisible = item.state == STATE_CLOSED - b.percentage.isVisible = item.state == STATE_CLOSED - - b.previewContainer.removeAllViews() - - val sum = item.typeCountMap.entries.sumBy { it.value }.toFloat() - item.typeCountMap.forEach { (type, count) -> - val layout = LinearLayout(contextWrapper) - val attendance = Attendance( - profileId = 0, - id = 0, - baseType = type, - typeName = "", - typeShort = manager.getTypeShort(type), - typeSymbol = manager.getTypeShort(type), - typeColor = manager.getAttendanceColor(type), - date = Date(0, 0, 0), - startTime = null, - semester = 0, - teacherId = 0, - subjectId = 0, - addedDate = 0 - ) - layout.addView(AttendanceView(contextWrapper, attendance, manager)) - layout.addView(TextView(contextWrapper).also { - it.setText(R.string.attendance_percentage_format, count/sum*100f) - it.setPadding(0, 0, 5.dp, 0) - }) - layout.setPadding(0, 8.dp, 0, 0) - b.previewContainer.addView(layout) - } + b.percentage.isVisible = true if (item.percentage == 0f) { b.percentage.isVisible = false b.percentage.text = null - b.summaryContainer.isVisible = false - b.summaryContainer.text = null } else { b.percentage.setText(R.string.attendance_percentage_format, item.percentage) - b.summaryContainer.setText(R.string.attendance_period_summary_format, item.percentage) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt index e77e5fbd..922831fd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull import pl.szczodrzynski.edziennik.startCoroutineTimer import kotlin.coroutines.CoroutineContext @@ -49,6 +50,12 @@ class AttendanceManager(val app: App) : CoroutineScope { else -> 0xff64b5f6.toInt() } } + fun getAttendanceColor(typeObject: AttendanceType): Int { + return (if (useSymbols) typeObject.typeColor else null) ?: when (typeObject.baseType) { + Attendance.TYPE_PRESENT_CUSTOM -> typeObject.typeColor ?: 0xff64b5f6.toInt() + else -> getAttendanceColor(typeObject.baseType) + } + } fun getAttendanceColor(attendance: Attendance): Int { return (if (useSymbols) attendance.typeColor else null) ?: when (attendance.baseType) { Attendance.TYPE_PRESENT_CUSTOM -> attendance.typeColor ?: 0xff64b5f6.toInt() diff --git a/app/src/main/res/layout/attendance_config_dialog.xml b/app/src/main/res/layout/attendance_config_dialog.xml index b31edc72..5d7db447 100644 --- a/app/src/main/res/layout/attendance_config_dialog.xml +++ b/app/src/main/res/layout/attendance_config_dialog.xml @@ -52,8 +52,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="32dp" - android:text="@string/attendance_config_show_presence_in_month" - android:visibility="gone" /> + android:text="@string/attendance_config_show_presence_in_month" /> diff --git a/app/src/main/res/layout/attendance_item_container_subject.xml b/app/src/main/res/layout/attendance_item_container_subject.xml new file mode 100644 index 00000000..bcf95152 --- /dev/null +++ b/app/src/main/res/layout/attendance_item_container_subject.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/attendance_list_fragment.xml b/app/src/main/res/layout/attendance_list_fragment.xml index 0d6e2abe..36f54733 100644 --- a/app/src/main/res/layout/attendance_list_fragment.xml +++ b/app/src/main/res/layout/attendance_list_fragment.xml @@ -7,41 +7,35 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> - - + + + + - - - - - - - - + android:layout_height="match_parent" + android:visibility="gone" + tools:listitem="@layout/attendance_item_attendance" + tools:visibility="visible" /> + diff --git a/app/src/main/res/layout/attendance_summary_fragment.xml b/app/src/main/res/layout/attendance_summary_fragment.xml index d3b1404e..e4d96e80 100644 --- a/app/src/main/res/layout/attendance_summary_fragment.xml +++ b/app/src/main/res/layout/attendance_summary_fragment.xml @@ -7,41 +7,165 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> - + android:layout_height="match_parent" + android:orientation="vertical"> - + android:layout_height="wrap_content" + android:layout_margin="8dp" + app:singleSelection="true" + app:selectionRequired="true" + android:gravity="center_horizontal"> - + android:text="Semestr 1" /> - + android:text="Semestr 2" /> - + + + + + - - + android:layout_height="wrap_content" + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c8c758958de64892bff13af13c934dff12936801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 7 May 2020 15:48:16 +0200 Subject: [PATCH 11/24] [API/Mobidziennik] Fix web attendance without lesson topic. --- .../java/pl/szczodrzynski/edziennik/data/api/Regexes.kt | 2 +- .../mobidziennik/data/web/MobidziennikWebAttendance.kt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt index 92aecbd8..82d59c4f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt @@ -84,7 +84,7 @@ object Regexes { """([0-9:]+) - .+? (.+?)""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_ATTENDANCE_LESSON by lazy { - """(.+?) - (.*?).+?.+?\((.+?), .+?(.+?)\)""".toRegex(DOT_MATCHES_ALL) + """(.+?)\s*\s*\((.+?),\s*(.+?)\)""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_HOMEWORK_ROW by lazy { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt index 9dd4638d..3389bf8e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt @@ -115,12 +115,12 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, val startTime = Time.fromH_m(range[1]) range[2].split(" / ").mapNotNull { Regexes.MOBIDZIENNIK_ATTENDANCE_LESSON.find(it) }.forEachIndexed { index, lesson -> - val topic = lesson[2] - if (topic.startsWith("Lekcja odwołana: ") || entry.isEmpty()) + val topic = lesson[1].substringAfter(" - ", missingDelimiterValue = "").takeIf { it.isNotBlank() } + if (topic?.startsWith("Lekcja odwołana: ") == true || entry.isEmpty()) return@forEachIndexed - val subjectName = lesson[1] + val subjectName = lesson[1].substringBefore(" - ") //val team = lesson[3] - val teacherName = lesson[4].fixName() + val teacherName = lesson[3].fixName() val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == teacherName }?.id ?: -1 val subjectId = data.subjectList.singleOrNull { it.longName == subjectName }?.id ?: -1 From c49755c0eba14e8d81c5f44990f94061c4e01a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 8 May 2020 20:46:51 +0200 Subject: [PATCH 12/24] [API/Librus] Fix messages login when ReCaptcha is needed. --- .../edziennik/data/api/Errors.kt | 1 + .../edziennik/librus/LibrusRecaptchaHelper.kt | 56 +++++++++++++++++++ .../librus/login/LibrusLoginMessages.kt | 15 +++++ app/src/main/res/values/errors.xml | 2 + 4 files changed, 74 insertions(+) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt index 268da24e..f676772b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt @@ -127,6 +127,7 @@ const val ERROR_LIBRUS_API_DEVICE_REGISTERED = 185 const val ERROR_LIBRUS_MESSAGES_NOT_FOUND = 186 const val ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST = 187 const val ERROR_LIBRUS_MESSAGES_ATTACHMENT_NOT_FOUND = 188 +const val ERROR_LOGIN_LIBRUS_MESSAGES_TIMEOUT = 189 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt new file mode 100644 index 00000000..94ab5ed1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus + +import android.content.Context +import android.webkit.WebView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.startCoroutineTimer +import kotlin.coroutines.CoroutineContext + +class LibrusRecaptchaHelper( + val context: Context, + url: String, + html: String, + val onSuccess: (url: String) -> Unit, + val onTimeout: () -> Unit +) : CoroutineScope { + companion object { + private const val TAG = "LibrusRecaptchaHelper" + } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + private val webView by lazy { + WebView(context).also { + it.settings.javaScriptEnabled = true + it.webViewClient = WebViewClient() + } + } + + private var timeout: Job? = null + + inner class WebViewClient : android.webkit.WebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + timeout?.cancel() + onSuccess(url) + return true + } + } + + init { + launch(Dispatchers.Main) { + webView.loadDataWithBaseURL(url, html, "text/html", "UTF-8", null) + } + timeout = startCoroutineTimer(delayMillis = 10000L) { + onTimeout() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt index ba8bcf76..94404232 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt @@ -10,6 +10,7 @@ import im.wangchao.mhttp.body.MediaTypeUtils import im.wangchao.mhttp.callback.TextCallbackHandler import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.LibrusRecaptchaHelper import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.utils.Utils.d @@ -35,6 +36,19 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { onSuccess() } + text?.contains("grecaptcha.ready") == true -> { + val url = response?.request()?.url()?.toString() ?: run { + data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) + return + } + + LibrusRecaptchaHelper(data.app, url, text, onSuccess = { newUrl -> + loginWithSynergia(newUrl) + }, onTimeout = { + data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_TIMEOUT, response, text) + }) + } + text?.contains("ok") == true -> { saveSessionId(response, text) onSuccess() @@ -46,6 +60,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { text?.contains("error") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text) text?.contains("eVarWhitThisNameNotExists") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) text?.contains("") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) + else -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) } } diff --git a/app/src/main/res/values/errors.xml b/app/src/main/res/values/errors.xml index 8e9e043b..db856eb0 100644 --- a/app/src/main/res/values/errors.xml +++ b/app/src/main/res/values/errors.xml @@ -99,6 +99,7 @@ ERROR_LIBRUS_MESSAGES_NOT_FOUND ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST ERROR_LIBRUS_MESSAGES_ATTACHMENT_NOT_FOUND + ERROR_LOGIN_LIBRUS_MESSAGES_TIMEOUT ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD @@ -277,6 +278,7 @@ Nie znaleziono wiadomości. Mogła zostać usunięta. Nieprawidłowe dane dostępu. Sprawdź poprawność wprowadzonych danych. Nie znaleziono załącznika. Mógł zostać usunięty. + Logowanie Librus Wiadomości: ReCaptcha przekroczono czas oczekiwania. Nieprawidłowy login lub hasło Podano stare hasło From 192dd0c4c7a6773e77991f11a7d8ff483fda2a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 8 May 2020 22:19:55 +0200 Subject: [PATCH 13/24] [UI] Add listing attendance by type. --- .../data/web/MobidziennikWebAttendance.kt | 13 ++- .../szczodrzynski/edziennik/data/db/AppDb.kt | 5 +- .../edziennik/data/db/entity/Attendance.kt | 2 + .../data/db/migration/Migration87.kt | 14 +++ .../modules/attendance/AttendanceAdapter.kt | 14 ++- .../modules/attendance/AttendanceFragment.kt | 13 ++- .../attendance/AttendanceListFragment.kt | 24 +++- .../attendance/AttendanceSummaryFragment.kt | 5 +- .../attendance/models/AttendanceTypeGroup.kt | 25 +++++ .../viewholder/AttendanceViewHolder.kt | 2 + .../viewholder/DayRangeViewHolder.kt | 4 +- .../attendance/viewholder/MonthViewHolder.kt | 6 +- .../viewholder/SubjectViewHolder.kt | 4 +- .../attendance/viewholder/TypeViewHolder.kt | 64 +++++++++++ ...iner.xml => attendance_item_day_range.xml} | 0 ...iner_bar.xml => attendance_item_month.xml} | 3 +- ...ubject.xml => attendance_item_subject.xml} | 0 .../main/res/layout/attendance_item_type.xml | 106 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 19 files changed, 280 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration87.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt rename app/src/main/res/layout/{attendance_item_container.xml => attendance_item_day_range.xml} (100%) rename app/src/main/res/layout/{attendance_item_container_bar.xml => attendance_item_month.xml} (98%) rename app/src/main/res/layout/{attendance_item_container_subject.xml => attendance_item_subject.xml} (100%) create mode 100644 app/src/main/res/layout/attendance_item_type.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt index 3389bf8e..498f6fbf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt @@ -15,6 +15,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSEN import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_BELATED import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT_CUSTOM import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_UNKNOWN import pl.szczodrzynski.edziennik.data.db.entity.Metadata @@ -132,13 +133,22 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, } entry = entry.removePrefix(typeSymbol) + var isCounted = true val baseType = when (typeSymbol) { "." -> TYPE_PRESENT "|" -> TYPE_ABSENT "+" -> TYPE_ABSENT_EXCUSED "s" -> TYPE_BELATED "z" -> TYPE_RELEASED - else -> TYPE_UNKNOWN + else -> { + isCounted = false + when (typeSymbol) { + "e" -> TYPE_PRESENT_CUSTOM + "en" -> TYPE_ABSENT + "ep" -> TYPE_PRESENT_CUSTOM + else -> TYPE_UNKNOWN + } + } } val typeName = types?.get(typeSymbol) ?: "" @@ -166,6 +176,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, subjectId = subjectId ).also { it.lessonTopic = topic + it.isCounted = isCounted } data.attendanceList.add(attendanceObject) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt index 0cd2fe26..b92f0e4a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt @@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.* LibrusLesson::class, TimetableManual::class, Metadata::class -], version = 86) +], version = 87) @TypeConverters( ConverterTime::class, ConverterDate::class, @@ -171,7 +171,8 @@ abstract class AppDb : RoomDatabase() { Migration83(), Migration84(), Migration85(), - Migration86() + Migration86(), + Migration87() ).allowMainThreadQueries().build() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt index 525c2b39..700b9682 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt @@ -65,6 +65,8 @@ open class Attendance( var lessonTopic: String? = null @ColumnInfo(name = "attendanceLessonNumber") var lessonNumber: Int? = null + @ColumnInfo(name = "attendanceIsCounted") + var isCounted: Boolean = true @Ignore var showAsUnseen: Boolean? = null diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration87.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration87.kt new file mode 100644 index 00000000..524cb559 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration87.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration87 : Migration(86, 87) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE attendances ADD COLUMN attendanceIsCounted INTEGER NOT NULL DEFAULT 1") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt index 812e8465..0dcae97e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.kt @@ -19,10 +19,7 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull import pl.szczodrzynski.edziennik.startCoroutineTimer -import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange -import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceEmpty -import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth -import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.* import pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder.* import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder @@ -39,7 +36,8 @@ class AttendanceAdapter( private const val ITEM_TYPE_DAY_RANGE = 1 private const val ITEM_TYPE_MONTH = 2 private const val ITEM_TYPE_SUBJECT = 3 - private const val ITEM_TYPE_EMPTY = 4 + private const val ITEM_TYPE_TYPE = 4 + private const val ITEM_TYPE_EMPTY = 5 const val STATE_CLOSED = 0 const val STATE_OPENED = 1 } @@ -60,6 +58,7 @@ class AttendanceAdapter( ITEM_TYPE_DAY_RANGE -> DayRangeViewHolder(inflater, parent) ITEM_TYPE_MONTH -> MonthViewHolder(inflater, parent) ITEM_TYPE_SUBJECT -> SubjectViewHolder(inflater, parent) + ITEM_TYPE_TYPE -> TypeViewHolder(inflater, parent) ITEM_TYPE_EMPTY -> EmptyViewHolder(inflater, parent) else -> throw IllegalArgumentException("Incorrect viewType") } @@ -71,6 +70,7 @@ class AttendanceAdapter( is AttendanceDayRange -> ITEM_TYPE_DAY_RANGE is AttendanceMonth -> ITEM_TYPE_MONTH is AttendanceSubject -> ITEM_TYPE_SUBJECT + is AttendanceTypeGroup -> ITEM_TYPE_TYPE is AttendanceEmpty -> ITEM_TYPE_EMPTY else -> throw IllegalArgumentException("Incorrect viewType") } @@ -102,7 +102,7 @@ class AttendanceAdapter( ).setDuration(200).start(); } - if (model is AttendanceDayRange || model is AttendanceMonth) { + if (model is AttendanceDayRange || model is AttendanceMonth || model is AttendanceTypeGroup) { // hide the preview, show summary val preview = view?.findViewById(R.id.previewContainer) val summary = view?.findViewById(R.id.summaryContainer) @@ -158,6 +158,7 @@ class AttendanceAdapter( is DayRangeViewHolder -> ITEM_TYPE_DAY_RANGE is MonthViewHolder -> ITEM_TYPE_MONTH is SubjectViewHolder -> ITEM_TYPE_SUBJECT + is TypeViewHolder -> ITEM_TYPE_TYPE is EmptyViewHolder -> ITEM_TYPE_EMPTY else -> throw IllegalArgumentException("Incorrect viewType") } @@ -170,6 +171,7 @@ class AttendanceAdapter( holder is DayRangeViewHolder && item is AttendanceDayRange -> holder.onBind(activity, app, item, position, this) holder is MonthViewHolder && item is AttendanceMonth -> holder.onBind(activity, app, item, position, this) holder is SubjectViewHolder && item is AttendanceSubject -> holder.onBind(activity, app, item, position, this) + holder is TypeViewHolder && item is AttendanceTypeGroup -> holder.onBind(activity, app, item, position, this) holder is EmptyViewHolder && item is AttendanceEmpty -> holder.onBind(activity, app, item, position, this) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt index e3d43c7e..0af177f9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceFragment.kt @@ -27,10 +27,11 @@ import kotlin.coroutines.CoroutineContext class AttendanceFragment : Fragment(), CoroutineScope { companion object { private const val TAG = "AttendanceFragment" - const val VIEW_DAYS = 0 - const val VIEW_MONTHS = 1 - const val VIEW_SUMMARY = 2 - const val VIEW_LIST = 3 + const val VIEW_SUMMARY = 0 + const val VIEW_DAYS = 1 + const val VIEW_MONTHS = 2 + const val VIEW_TYPES = 3 + const val VIEW_LIST = 4 var pageSelection = 1 } @@ -93,6 +94,10 @@ class AttendanceFragment : Fragment(), CoroutineScope { arguments = Bundle("viewType" to VIEW_MONTHS) } to getString(R.string.attendance_tab_months), + AttendanceListFragment().apply { + arguments = Bundle("viewType" to VIEW_TYPES) + } to getString(R.string.attendance_tab_types), + AttendanceListFragment().apply { arguments = Bundle("viewType" to VIEW_LIST) } to getString(R.string.attendance_tab_list) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt index 2f800bb0..d317f4e9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt @@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.startCoroutineTimer import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceTypeGroup import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject import pl.szczodrzynski.edziennik.utils.models.Date @@ -146,7 +147,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { var element = iterator.next() while (iterator.hasNext()) { var nextElement = iterator.next() - while (Date.diffDays(element.rangeStart, nextElement.rangeStart) <= 1) { + while (Date.diffDays(element.rangeStart, nextElement.rangeStart) <= 1 && iterator.hasNext()) { if (element.rangeEnd == null) element.rangeEnd = element.rangeStart @@ -173,7 +174,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { items.forEach { month -> month.typeCountMap = month.items .groupBy { it.typeObject } - .map { it.key to it.value.size } + .map { it.key to it.value.count { a -> a.isCounted } } .sortedBy { it.first } .toMap() @@ -205,6 +206,25 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { return items.toMutableList() } + else if (viewType == AttendanceFragment.VIEW_TYPES) { + val items = attendance + .groupBy { it.typeObject } + .map { AttendanceTypeGroup( + type = it.key, + items = it.value.toMutableList() + ) } + + items.forEach { type -> + type.percentage = if (attendance.isEmpty()) + 0f + else + type.items.size.toFloat() / attendance.size.toFloat() * 100f + + type.semesterCount = type.items.count { it.semester == app.profile.currentSemester } + } + + return items.toMutableList() + } return attendance.filter { it.baseType != Attendance.TYPE_PRESENT }.toMutableList() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt index 2e4378c8..9e610cda 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt @@ -78,6 +78,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { b.list.apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context) + isNestedScrollingEnabled = false } } adapter.notifyDataSetChanged() @@ -183,7 +184,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { items.forEach { subject -> subject.typeCountMap = subject.items .groupBy { it.typeObject } - .map { it.key to it.value.size } + .map { it.key to it.value.count { a -> a.isCounted } } .sortedBy { it.first } .toMap() @@ -217,7 +218,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { val typeCountMap = attendance .groupBy { it.typeObject } - .map { it.key to it.value.size } + .map { it.key to it.value.count { a -> a.isCounted } } .sortedBy { it.first } .toMap() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt new file mode 100644 index 00000000..d6b431ec --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/models/AttendanceTypeGroup.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.models + +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel + +data class AttendanceTypeGroup( + val type: AttendanceType, + override val items: MutableList = mutableListOf() +) : ExpandableItemModel(items) { + override var level = 1 + + var lastAddedDate = 0L + + var hasUnseen: Boolean = false + get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen } + + var percentage: Float = 0f + var semesterCount: Int = 0 +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt index e67c68b7..3e7a1d32 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/AttendanceViewHolder.kt @@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Week class AttendanceViewHolder( inflater: LayoutInflater, @@ -42,6 +43,7 @@ class AttendanceViewHolder( b.type.text = item.typeName b.subjectName.text = item.subjectLongName ?: item.lessonTopic b.dateTime.text = listOf( + Week.getFullDayName(item.date.weekDay), item.date.formattedStringShort, item.startTime?.stringHM, item.lessonNumber?.let { app.getString(R.string.attendance_lesson_number_format, it) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt index 11a5005c..b42d5e16 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/DayRangeViewHolder.kt @@ -16,7 +16,7 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.concat import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBinding +import pl.szczodrzynski.edziennik.databinding.AttendanceItemDayRangeBinding import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView @@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.utils.Themes class DayRangeViewHolder( inflater: LayoutInflater, parent: ViewGroup, - val b: AttendanceItemContainerBinding = AttendanceItemContainerBinding.inflate(inflater, parent, false) + val b: AttendanceItemDayRangeBinding = AttendanceItemDayRangeBinding.inflate(inflater, parent, false) ) : RecyclerView.ViewHolder(b.root), BindableViewHolder { companion object { private const val TAG = "DayRangeViewHolder" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt index 8c010f81..08d2acec 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt @@ -15,7 +15,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.db.entity.Attendance -import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBarBinding +import pl.szczodrzynski.edziennik.databinding.AttendanceItemMonthBinding import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView @@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date class MonthViewHolder( inflater: LayoutInflater, parent: ViewGroup, - val b: AttendanceItemContainerBarBinding = AttendanceItemContainerBarBinding.inflate(inflater, parent, false) + val b: AttendanceItemMonthBinding = AttendanceItemMonthBinding.inflate(inflater, parent, false) ) : RecyclerView.ViewHolder(b.root), BindableViewHolder { companion object { private const val TAG = "MonthViewHolder" @@ -80,7 +80,7 @@ class MonthViewHolder( it.setText(R.string.attendance_percentage_format, count/sum*100f) it.setPadding(0, 0, 5.dp, 0) }) - layout.setPadding(0, 8.dp, 0, 8.dp) + layout.setPadding(0, 8.dp, 0, 0) b.previewContainer.addView(layout) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt index bea33ed4..9a8e56a0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt @@ -12,7 +12,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerSubjectBinding +import pl.szczodrzynski.edziennik.databinding.AttendanceItemSubjectBinding import pl.szczodrzynski.edziennik.setText import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED @@ -23,7 +23,7 @@ import pl.szczodrzynski.edziennik.utils.Themes class SubjectViewHolder( inflater: LayoutInflater, parent: ViewGroup, - val b: AttendanceItemContainerSubjectBinding = AttendanceItemContainerSubjectBinding.inflate(inflater, parent, false) + val b: AttendanceItemSubjectBinding = AttendanceItemSubjectBinding.inflate(inflater, parent, false) ) : RecyclerView.ViewHolder(b.root), BindableViewHolder { companion object { private const val TAG = "SubjectViewHolder" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt new file mode 100644 index 00000000..62465e8b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-8. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.databinding.AttendanceItemTypeBinding +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceTypeGroup +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Date + +class TypeViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: AttendanceItemTypeBinding = AttendanceItemTypeBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "TypeViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceTypeGroup, position: Int, adapter: AttendanceAdapter) { + val manager = app.attendanceManager + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + val type = item.type + b.title.text = type.typeName + + b.dropdownIcon.rotation = when (item.state) { + AttendanceAdapter.STATE_CLOSED -> 0f + else -> 180f + } + + b.unread.isVisible = item.hasUnseen + + b.previewContainer.visibility = if (item.state == AttendanceAdapter.STATE_CLOSED) View.VISIBLE else View.INVISIBLE + + b.type.setAttendance(Attendance( + profileId = 0, + id = 0, + baseType = type.baseType, + typeName = "", + typeShort = type.typeShort, + typeSymbol = type.typeSymbol, + typeColor = type.typeColor, + date = Date(0, 0, 0), + startTime = null, + semester = 0, + teacherId = 0, + subjectId = 0, + addedDate = 0 + ), manager, bigView = false) + } +} diff --git a/app/src/main/res/layout/attendance_item_container.xml b/app/src/main/res/layout/attendance_item_day_range.xml similarity index 100% rename from app/src/main/res/layout/attendance_item_container.xml rename to app/src/main/res/layout/attendance_item_day_range.xml diff --git a/app/src/main/res/layout/attendance_item_container_bar.xml b/app/src/main/res/layout/attendance_item_month.xml similarity index 98% rename from app/src/main/res/layout/attendance_item_container_bar.xml rename to app/src/main/res/layout/attendance_item_month.xml index 63ff39ed..6016a81c 100644 --- a/app/src/main/res/layout/attendance_item_container_bar.xml +++ b/app/src/main/res/layout/attendance_item_month.xml @@ -86,6 +86,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="8dp" android:orientation="horizontal" + android:paddingBottom="8dp" android:visibility="gone" app:flexWrap="wrap" tools:visibility="visible"> @@ -93,7 +94,7 @@ + android:paddingTop="8dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f97b5d0..bc603679 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1304,4 +1304,5 @@ wczoraj Jesteś offline. Spróbuj włączyć Wi-Fi lub dane komórkowe. Połączenie sieciowe + Wg typu From a5461a488a63d99b295034a9c8a0058406e9ebc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 9 May 2020 10:53:19 +0200 Subject: [PATCH 14/24] [UI] Fix attendance type list crash. --- app/src/main/res/layout/attendance_item_type.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/layout/attendance_item_type.xml b/app/src/main/res/layout/attendance_item_type.xml index 6df1cf05..e524fa18 100644 --- a/app/src/main/res/layout/attendance_item_type.xml +++ b/app/src/main/res/layout/attendance_item_type.xml @@ -26,22 +26,22 @@ android:gravity="center_vertical" android:orientation="horizontal"> - + android:layout_height="wrap_content" + android:gravity="center_horizontal"> - + Date: Sat, 9 May 2020 10:59:25 +0200 Subject: [PATCH 15/24] [DB] Force attendance sync in models migration. --- .../pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt index 2df57788..add43d71 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration86.kt @@ -172,5 +172,7 @@ class Migration86 : Migration(85, 86) { database.execSQL("""CREATE UNIQUE INDEX index_metadata_profileId_thingType_thingId ON "metadata" (profileId, thingType, thingId)""") database.execSQL("""INSERT INTO metadata (profileId, metadataId, thingType, thingId, seen, notified) SELECT profileId, metadataId, thingType, thingId, seen, notified FROM _metadata""") database.execSQL("""DROP TABLE _metadata""") + + database.execSQL("""DELETE FROM endpointTimers WHERE endpointId IN (1080, 1081, 2050, 1090, 1010, 1014);""") } } From 65e0e10db6d0c207c321212ec5362d9c07eff760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 9 May 2020 15:23:44 +0200 Subject: [PATCH 16/24] [4.1] Update build.gradle, signing and changelog. --- app/src/main/assets/pl-changelog.html | 30 +++---------------- app/src/main/cpp/szkolny-signing.cpp | 2 +- .../data/api/szkolny/interceptor/Signing.kt | 2 +- build.gradle | 4 +-- 4 files changed, 8 insertions(+), 30 deletions(-) diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index 3fa725f2..4929ebcc 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,30 +1,8 @@ -

Wersja 4.0, 2020-04-19

+

Wersja 4.1, 2020-05-09

    -
  • Wysyłanie wiadomości - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli 👏
  • -
  • Przebudowaliśmy cały moduł synchronizacji, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych
  • -
  • Udoskonalony wygląd Szkolnego - sprawi, że korzystanie z aplikacji będzie jeszcze przyjemniejsze
  • -
  • Wyszukiwarka wiadomości, pozwalająca na łatwe znalezienie potrzebnej konwersacji.
  • -
  • Możliwość pobierania załączników do zadań domowych oraz wiadomości w każdym dzienniku.
  • -
  • Nowa Strona główna - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu
  • -
  • Nowy Plan lekcji - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie
  • -
  • Nowe Oceny - z możliwością zmiany wartości plusów oraz minusów oraz wyłączenia niektórych ocen ze średniej
  • -
  • Opcja wyłączenia wybranych powiadomień z aplikacji
  • -
  • Znaczki nieprzeczytanych informacji na obrazkach profili.
  • -
    -
    -
  • Udoskonalone tłumaczenie na j.angielski (dzięki @Predator)
  • -
  • Nowe okienka informacji o wydarzeniach oraz lekcjach
  • -
  • Nowe, przyjemniejsze powiadomienia
  • -
  • Dużo poprawek w widoku Wiadomości oraz Ogłoszeń
  • -
  • Częściowa Obsługa dziennika EduDziennik
  • -
  • Librus: opcja logowania w dziennikach Jednostek Samorządu Terytorialnego oraz Oświata w Radomiu
  • -
  • Librus: poprawione obliczanie frekwencji
  • -
  • Librus: obsługa Zadań domowych bez posiadania Mobilnych dodatków (przez system Synergia)
  • -
  • Lepsze przekazywanie powiadomień na komputer oraz łatwiejsze parowanie
  • -
  • Łatwiejsze dodawanie własnych wydarzeń
  • -
  • Poprawiliśmy synchronizację w tle na niektórych telefonach
  • -
  • Usunąłem denerwujący brak zaznaczenia w lewym menu
  • -
  • Znaczna ilość błędów z poprzednich wersji już nie występuje
  • +
  • Naprawiona synchronizacja Wiadomości w Librusie.
  • +
  • Widok adresu dołączenia do lekcji online w kalendarzu (jeżeli nauczyciel wpisze adres).
  • +
  • Nowy moduł Frekwencji, obsługujący również lekcje zdalne.


diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index 6463e8af..f06d86b7 100644 --- a/app/src/main/cpp/szkolny-signing.cpp +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -9,7 +9,7 @@ /*secret password - removed for source code publication*/ static toys AES_IV[16] = { - 0x38, 0xd4, 0x73, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + 0xec, 0xc0, 0x3e, 0x4e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index a6412ba6..3ee19992 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -46,6 +46,6 @@ object Signing { /*fun provideKey(param1: String, param2: Long): ByteArray {*/ fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { - return "$param1.MTIzNDU2Nzg5MDP/4SAI6B===.$param2".sha256() + return "$param1.MTIzNDU2Nzg5MDwwzwp5Gx===.$param2".sha256() } } diff --git a/build.gradle b/build.gradle index ffac6f70..f4b6db5e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { kotlin_version = '1.3.61' release = [ - versionName: "4.0", - versionCode: 4000099 + versionName: "4.1", + versionCode: 4010099 ] setup = [ From 771712da992a971e061abae27b540c82324c99be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 9 May 2020 19:18:18 +0200 Subject: [PATCH 17/24] [UI/Attendance] Fix counting issues. Add attendance details dialog. --- .../data/web/MobidziennikWebAttendance.kt | 16 +- .../edziennik/data/db/entity/Attendance.kt | 2 +- .../data/db/entity/AttendanceType.kt | 4 + .../ui/modules/attendance/AttendanceBar.kt | 12 +- .../attendance/AttendanceDetailsDialog.kt | 64 +++++ .../attendance/AttendanceListFragment.kt | 14 +- .../attendance/AttendanceSummaryFragment.kt | 24 +- .../attendance/viewholder/MonthViewHolder.kt | 5 +- .../viewholder/SubjectViewHolder.kt | 2 +- .../attendance/viewholder/TypeViewHolder.kt | 9 +- .../utils/managers/AttendanceManager.kt | 16 +- .../res/layout/attendance_details_dialog.xml | 235 ++++++++++++++++++ .../main/res/layout/attendance_item_type.xml | 31 +-- app/src/main/res/values/strings.xml | 10 + 14 files changed, 381 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt create mode 100644 app/src/main/res/layout/attendance_details_dialog.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt index 498f6fbf..36dbe1e9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt @@ -151,11 +151,17 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, } } val typeName = types?.get(typeSymbol) ?: "" + val typeColor = when (typeSymbol) { + "e" -> 0xff673ab7 + "en" -> 0xffec407a + "ep" -> 0xff4caf50 + else -> null + }?.toInt() - val typeShort = when (baseType) { - TYPE_UNKNOWN -> typeSymbol - else -> data.app.attendanceManager.getTypeShort(baseType) - } + val typeShort = if (isCounted) + data.app.attendanceManager.getTypeShort(baseType) + else + typeSymbol val semester = data.profile?.dateToSemester(lessonDate) ?: 1 @@ -168,7 +174,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, typeName = typeName, typeShort = typeShort, typeSymbol = typeSymbol, - typeColor = null, + typeColor = typeColor, date = lessonDate, startTime = startTime, semester = semester, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt index 700b9682..b2ebb5a0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Attendance.kt @@ -73,6 +73,6 @@ open class Attendance( @delegate:Ignore val typeObject by lazy { - AttendanceType(profileId, baseType.toLong(), baseType, typeName, typeShort, typeSymbol, typeColor) + AttendanceType(profileId, baseType.toLong(), baseType, typeName, typeShort, typeSymbol, typeColor).also { it.isCounted = isCounted } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt index 904c06c6..c5fccc4b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/AttendanceType.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.data.db.entity import androidx.room.Entity +import androidx.room.Ignore @Entity(tableName = "attendanceTypes", primaryKeys = ["profileId", "id"]) @@ -23,6 +24,9 @@ data class AttendanceType ( val typeColor: Int? ) : Comparable { + @Ignore + var isCounted: Boolean = true + // attendance bar order: // day_free, present, present_custom, unknown, belated_excused, belated, released, absent_excused, absent, override fun compareTo(other: AttendanceType): Int { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt index bfe49a41..70be9256 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceBar.kt @@ -12,6 +12,7 @@ import android.util.AttributeSet import android.view.View import pl.szczodrzynski.edziennik.dp import pl.szczodrzynski.edziennik.utils.Colors +import kotlin.math.roundToInt /* https://github.com/JakubekWeg/Mobishit/blob/master/app/src/main/java/jakubweg/mobishit/view/AttendanceBarView.kt */ class AttendanceBar : View { @@ -34,7 +35,7 @@ class AttendanceBar : View { mCornerRadius = 4.dp.toFloat() if (isInEditMode) - setAttendanceData(mapOf( + setAttendanceData(listOf( 0xff43a047.toInt() to 23, 0xff009688.toInt() to 187, 0xff3f51b5.toInt() to 46, @@ -49,8 +50,8 @@ class AttendanceBar : View { // color, count private class AttendanceItem(var color: Int, var count: Int) - fun setAttendanceData(list: Map) { - attendancesList = list.map { AttendanceItem(it.key, it.value) } + fun setAttendanceData(list: List>) { + attendancesList = list.map { AttendanceItem(it.first, it.second) } setWillNotDraw(false) invalidate() } @@ -91,11 +92,12 @@ class AttendanceBar : View { mainPaint.color = e.color canvas.drawRect(left, top, left + width, bottom, mainPaint) + val percentage = (100f * e.count / sum).roundToInt().toString() + "%" val textBounds = Rect() - textPaint.getTextBounds(e.count.toString(), 0, e.count.toString().length, textBounds) + textPaint.getTextBounds(percentage, 0, percentage.length, textBounds) if (width > textBounds.width() + 8.dp && height > textBounds.height() + 2.dp) { textPaint.color = Colors.legibleTextColor(e.color) - canvas.drawText(e.count.toString(), left + width / 2, bottom - height / 2 + textBounds.height()/2, textPaint) + canvas.drawText(percentage, left + width / 2, bottom - height / 2 + textBounds.height()/2, textPaint) } left += width diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt new file mode 100644 index 00000000..ce49efbd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-9. + */ + +package pl.szczodrzynski.edziennik.ui.modules.attendance + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.ColorUtils +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull +import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding +import pl.szczodrzynski.edziennik.setTintColor +import kotlin.coroutines.CoroutineContext + +class AttendanceDetailsDialog( + val activity: AppCompatActivity, + val attendance: AttendanceFull, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "AttendanceDetailsDialog" + } + + private lateinit var app: App + private lateinit var b: AttendanceDetailsDialogBinding + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local variables go here + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + b = AttendanceDetailsDialogBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + val manager = app.attendanceManager + + val attendanceColor = manager.getAttendanceColor(attendance) + b.attendance = attendance + b.devMode = App.debugMode + b.attendanceName.setTextColor(if (ColorUtils.calculateLuminance(attendanceColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt()) + b.attendanceName.background.setTintColor(attendanceColor) + + b.attendanceIsCounted.setText(if (attendance.isCounted) R.string.yes else R.string.no) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt index d317f4e9..8e68cdbb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt @@ -95,7 +95,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { }}) adapter.onAttendanceClick = { - //GradeDetailsDialog(activity, it) + AttendanceDetailsDialog(activity, it) } }; return true} @@ -174,15 +174,14 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { items.forEach { month -> month.typeCountMap = month.items .groupBy { it.typeObject } - .map { it.key to it.value.count { a -> a.isCounted } } + .map { it.key to it.value.size } .sortedBy { it.first } .toMap() val totalCount = month.typeCountMap.entries.sumBy { - when (it.key.baseType) { - Attendance.TYPE_UNKNOWN -> 0 - else -> it.value - } + if (!it.key.isCounted || it.key.baseType == Attendance.TYPE_UNKNOWN) + 0 + else it.value } val presenceCount = month.typeCountMap.entries.sumBy { when (it.key.baseType) { @@ -190,7 +189,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { Attendance.TYPE_PRESENT_CUSTOM, Attendance.TYPE_BELATED, Attendance.TYPE_BELATED_EXCUSED, - Attendance.TYPE_RELEASED -> it.value + Attendance.TYPE_RELEASED -> if (it.key.isCounted) it.value else 0 else -> 0 } } @@ -213,6 +212,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { type = it.key, items = it.value.toMutableList() ) } + .sortedBy { it.items.size } items.forEach { type -> type.percentage = if (attendance.isEmpty()) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt index 9e610cda..5f61020b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt @@ -76,7 +76,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { if (adapter.items.isNotNullNorEmpty() && b.list.adapter == null) { b.list.adapter = adapter b.list.apply { - setHasFixedSize(true) + setHasFixedSize(false) layoutManager = LinearLayoutManager(context) isNestedScrollingEnabled = false } @@ -103,7 +103,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { }}) adapter.onAttendanceClick = { - //GradeDetailsDialog(activity, it) + AttendanceDetailsDialog(activity, it) } b.toggleGroup.check(when (periodSelection) { @@ -184,15 +184,14 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { items.forEach { subject -> subject.typeCountMap = subject.items .groupBy { it.typeObject } - .map { it.key to it.value.count { a -> a.isCounted } } + .map { it.key to it.value.size } .sortedBy { it.first } .toMap() val totalCount = subject.typeCountMap.entries.sumBy { - when (it.key.baseType) { - Attendance.TYPE_UNKNOWN -> 0 - else -> it.value - } + if (!it.key.isCounted || it.key.baseType == Attendance.TYPE_UNKNOWN) + 0 + else it.value } val presenceCount = subject.typeCountMap.entries.sumBy { when (it.key.baseType) { @@ -200,7 +199,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { Attendance.TYPE_PRESENT_CUSTOM, Attendance.TYPE_BELATED, Attendance.TYPE_BELATED_EXCUSED, - Attendance.TYPE_RELEASED -> it.value + Attendance.TYPE_RELEASED -> if (it.key.isCounted) it.value else 0 else -> 0 } } @@ -218,7 +217,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { val typeCountMap = attendance .groupBy { it.typeObject } - .map { it.key to it.value.count { a -> a.isCounted } } + .map { it.key to it.value.size } .sortedBy { it.first } .toMap() @@ -228,11 +227,11 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { presenceCountSum.toFloat() / totalCountSum.toFloat() * 100f launch { - b.attendanceBar.setAttendanceData(typeCountMap.mapKeys { manager.getAttendanceColor(it.key) }) + b.attendanceBar.setAttendanceData(typeCountMap.map { manager.getAttendanceColor(it.key) to it.value }) b.attendanceBar.isInvisible = typeCountMap.isEmpty() b.previewContainer.removeAllViews() - val sum = typeCountMap.entries.sumBy { it.value }.toFloat() + //val sum = typeCountMap.entries.sumBy { it.value }.toFloat() typeCountMap.forEach { (type, count) -> val layout = LinearLayout(activity) val attendanceObject = Attendance( @@ -252,7 +251,8 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { ) layout.addView(AttendanceView(activity, attendanceObject, manager)) layout.addView(TextView(activity).also { - it.setText(R.string.attendance_percentage_format, count/sum*100f) + //it.setText(R.string.attendance_percentage_format, count/sum*100f) + it.text = count.toString() it.setPadding(0, 0, 5.dp, 0) }) layout.setPadding(0, 8.dp, 0, 8.dp) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt index 08d2acec..71c139ac 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/MonthViewHolder.kt @@ -49,7 +49,7 @@ class MonthViewHolder( b.unread.isVisible = item.hasUnseen - b.attendanceBar.setAttendanceData(item.typeCountMap.mapKeys { manager.getAttendanceColor(it.key) }) + b.attendanceBar.setAttendanceData(item.typeCountMap.map { manager.getAttendanceColor(it.key) to it.value }) b.previewContainer.isInvisible = item.state != STATE_CLOSED b.summaryContainer.isInvisible = item.state == STATE_CLOSED @@ -77,7 +77,8 @@ class MonthViewHolder( ) layout.addView(AttendanceView(contextWrapper, attendance, manager)) layout.addView(TextView(contextWrapper).also { - it.setText(R.string.attendance_percentage_format, count/sum*100f) + //it.setText(R.string.attendance_percentage_format, count/sum*100f) + it.text = count.toString() it.setPadding(0, 0, 5.dp, 0) }) layout.setPadding(0, 8.dp, 0, 0) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt index 9a8e56a0..e439111d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/SubjectViewHolder.kt @@ -42,7 +42,7 @@ class SubjectViewHolder( b.unread.isVisible = item.hasUnseen - b.attendanceBar.setAttendanceData(item.typeCountMap.mapKeys { manager.getAttendanceColor(it.key) }) + b.attendanceBar.setAttendanceData(item.typeCountMap.map { manager.getAttendanceColor(it.key) to it.value }) b.percentage.isVisible = true diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt index 62465e8b..a2f8b689 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/viewholder/TypeViewHolder.kt @@ -5,13 +5,14 @@ package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ContextThemeWrapper import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.concat import pl.szczodrzynski.edziennik.data.db.entity.Attendance import pl.szczodrzynski.edziennik.databinding.AttendanceItemTypeBinding import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter @@ -43,7 +44,11 @@ class TypeViewHolder( b.unread.isVisible = item.hasUnseen - b.previewContainer.visibility = if (item.state == AttendanceAdapter.STATE_CLOSED) View.VISIBLE else View.INVISIBLE + b.details.text = listOf( + app.getString(R.string.attendance_percentage_format, item.percentage), + app.getString(R.string.attendance_type_yearly_format, item.items.size), + app.getString(R.string.attendance_type_semester_format, item.semesterCount) + ).concat(" • ") b.type.setAttendance(Attendance( profileId = 0, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt index 922831fd..cd726797 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt @@ -51,16 +51,16 @@ class AttendanceManager(val app: App) : CoroutineScope { } } fun getAttendanceColor(typeObject: AttendanceType): Int { - return (if (useSymbols) typeObject.typeColor else null) ?: when (typeObject.baseType) { - Attendance.TYPE_PRESENT_CUSTOM -> typeObject.typeColor ?: 0xff64b5f6.toInt() - else -> getAttendanceColor(typeObject.baseType) - } + return (if (useSymbols) typeObject.typeColor else null) + ?: if (typeObject.baseType == Attendance.TYPE_PRESENT_CUSTOM || !typeObject.isCounted) + typeObject.typeColor ?: 0xff64b5f6.toInt() + else getAttendanceColor(typeObject.baseType) } fun getAttendanceColor(attendance: Attendance): Int { - return (if (useSymbols) attendance.typeColor else null) ?: when (attendance.baseType) { - Attendance.TYPE_PRESENT_CUSTOM -> attendance.typeColor ?: 0xff64b5f6.toInt() - else -> getAttendanceColor(attendance.baseType) - } + return (if (useSymbols) attendance.typeColor else null) + ?: if (attendance.baseType == Attendance.TYPE_PRESENT_CUSTOM || !attendance.isCounted) + attendance.typeColor ?: 0xff64b5f6.toInt() + else getAttendanceColor(attendance.baseType) } /* _ _ _____ _____ _ __ _ diff --git a/app/src/main/res/layout/attendance_details_dialog.xml b/app/src/main/res/layout/attendance_details_dialog.xml new file mode 100644 index 00000000..59ec37c3 --- /dev/null +++ b/app/src/main/res/layout/attendance_details_dialog.xml @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/attendance_item_type.xml b/app/src/main/res/layout/attendance_item_type.xml index e524fa18..2ec5cd4e 100644 --- a/app/src/main/res/layout/attendance_item_type.xml +++ b/app/src/main/res/layout/attendance_item_type.xml @@ -27,7 +27,7 @@ android:orientation="horizontal"> @@ -81,26 +81,17 @@ tools:background="@android:drawable/ic_menu_more" /> - - - - + android:layout_gravity="center_vertical" + android:layout_marginHorizontal="8dp" + android:layout_marginBottom="8dp" + android:textAppearance="@style/NavView.TextView.Helper" + android:textSize="14sp" + tools:text="57,67% • 521 przez cały rok • 134 w tym semestrze" + tools:text1="Cały rok: 3 oceny • suma: 320 pkt" + tools:text2="Cały rok: 15 ocen • średnia: 2,62" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bc603679..e2000163 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1305,4 +1305,14 @@ Jesteś offline. Spróbuj włączyć Wi-Fi lub dane komórkowe. Połączenie sieciowe Wg typu + %d przez cały rok + %d w tym semestrze + Nauczyciel + Rodzaj + Data + Godzina + Temat lekcji + Liczona do puli? + ID frekwencji + ID rodzaju podstawowego From 328b20f78b4321cc2f694d9848cdd5a001fb49d0 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Sat, 9 May 2020 19:54:19 +0200 Subject: [PATCH 18/24] [Fix] Increase HTTP client timeout duration. --- app/src/main/java/pl/szczodrzynski/edziennik/App.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt index 051dff38..3a9c0725 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -101,9 +101,9 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { .followSslRedirects(true) .retryOnConnectionFailure(true) .cookieJar(cookieJar) - .connectTimeout(20, TimeUnit.SECONDS) - .writeTimeout(5, TimeUnit.SECONDS) - .readTimeout(10, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) builder.installHttpsSupport(this) if (debugMode || BuildConfig.DEBUG) { From a6ce3a5068cd460cb1fd563d698dfaa37dc4d011 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Sat, 9 May 2020 19:56:05 +0200 Subject: [PATCH 19/24] [API/Librus] Make downloading attachments use Synergia. --- .../edziennik/data/api/Constants.kt | 1 + .../data/api/edziennik/librus/Librus.kt | 10 +++----- .../edziennik/librus/data/LibrusSynergia.kt | 2 ++ .../synergia/LibrusSynergiaGetAttachment.kt | 24 +++++++++++++++++++ 4 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt index 81cd81cf..6d894dbd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt @@ -57,6 +57,7 @@ const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module" const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action=" const val LIBRUS_SYNERGIA_HOMEWORK_ATTACHMENT_URL = "https://synergia.librus.pl/homework/downloadFile" +const val LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL = "https://synergia.librus.pl/wiadomosci/pobierz_zalacznik" const val IDZIENNIK_USER_AGENT = SYNERGIA_USER_AGENT const val IDZIENNIK_WEB_URL = "https://iuczniowie.progman.pl/idziennik" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt index a79a66f8..48613079 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt @@ -9,13 +9,9 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusData import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api.LibrusApiAnnouncementMarkAsRead -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetAttachment -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetMessage import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetRecipientList import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesSendMessage -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaGetHomework -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaHomeworkGetAttachment -import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaMarkAllAnnouncementsAsRead +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.firstlogin.LibrusFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLogin import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback @@ -124,8 +120,8 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) { when (owner) { is Message -> { - login(LOGIN_METHOD_LIBRUS_MESSAGES) { - LibrusMessagesGetAttachment(data, owner, attachmentId, attachmentName) { + login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + LibrusSynergiaGetAttachment(data, owner, attachmentId, attachmentName) { completed() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt index 96f28a65..72faf801 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt @@ -91,6 +91,8 @@ open class LibrusSynergia(open val data: DataLibrus, open val lastSync: Long?) { } fun redirectUrlGet(tag: String, url: String, onSuccess: (url: String) -> Unit) { + d(tag, "Request: Librus/Synergia - $url") + val callback = object : TextCallbackHandler() { override fun onSuccess(text: String?, response: Response) { val redirectUrl = response.headers().get("Location") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt new file mode 100644 index 00000000..6b1f2eba --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetAttachment.kt @@ -0,0 +1,24 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import pl.szczodrzynski.edziennik.data.api.LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusSandboxDownloadAttachment +import pl.szczodrzynski.edziennik.data.db.entity.Message + +class LibrusSynergiaGetAttachment(override val data: DataLibrus, + val message: Message, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaGetAttachment" + } + + init { + redirectUrlGet(TAG, "$LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL/${message.id}/$attachmentId") { url -> + LibrusSandboxDownloadAttachment(data, url, message, attachmentId, attachmentName, onSuccess) + } + } +} From 1a8134459a9839139a631e19fb4a39cfa6eaded9 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Sat, 9 May 2020 20:10:48 +0200 Subject: [PATCH 20/24] [API/Librus] Implement downloading messages using Synergia endpoints. --- .../edziennik/data/api/Regexes.kt | 3 + .../data/api/edziennik/librus/Librus.kt | 4 +- .../api/edziennik/librus/LibrusFeatures.kt | 2 + .../api/edziennik/librus/data/LibrusData.kt | 9 + .../data/synergia/LibrusSynergiaGetMessage.kt | 160 ++++++++++++++++++ .../synergia/LibrusSynergiaGetMessages.kt | 116 +++++++++++++ 6 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt index 82d59c4f..7d4d5c39 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt @@ -148,6 +148,9 @@ object Regexes { val LIBRUS_ATTACHMENT_KEY by lazy { """singleUseKey=([0-9A-z_]+)""".toRegex() } + val LIBRUS_MESSAGE_ID by lazy { + """/wiadomosci/[0-9]+/[0-9]+/([0-9]+?)/""".toRegex() + } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt index 48613079..a9a107b9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt @@ -86,8 +86,8 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va } override fun getMessage(message: MessageFull) { - login(LOGIN_METHOD_LIBRUS_MESSAGES) { - LibrusMessagesGetMessage(data, message) { + login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + LibrusSynergiaGetMessage(data, message) { completed() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt index 98caab4e..c14a00df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt @@ -50,6 +50,8 @@ const val ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS = 1130 const val ENDPOINT_LIBRUS_SYNERGIA_INFO = 2010 const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 2020 const val ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK = 2030 +const val ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_RECEIVED = 2040 +const val ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_SENT = 2050 const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 3010 const val ENDPOINT_LIBRUS_MESSAGES_SENT = 3020 const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt index bf1fdab4..119c22b9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt @@ -8,6 +8,7 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.edziennik.librus.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetList +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaGetMessages import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaHomework import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaInfo import pl.szczodrzynski.edziennik.data.db.entity.Message @@ -201,6 +202,14 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) { data.startProgress(R.string.edziennik_progress_endpoint_student_info) LibrusSynergiaInfo(data, lastSync, onSuccess) } + ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_RECEIVED -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) + LibrusSynergiaGetMessages(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + } + ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_SENT -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) + LibrusSynergiaGetMessages(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + } /** * MESSAGES diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt new file mode 100644 index 00000000..4cb1e246 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessage.kt @@ -0,0 +1,160 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import org.greenrobot.eventbus.EventBus +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.swapFirstLastName +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusSynergiaGetMessage(override val data: DataLibrus, + private val messageObject: MessageFull, + val onSuccess: () -> Unit) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaGetMessage" + } + + init { + val endpoint = when (messageObject.type) { + Message.TYPE_SENT -> "wiadomosci/1/6/${messageObject.id}/f0" + else -> "wiadomosci/1/5/${messageObject.id}/f0" + } + + data.profile?.also { profile -> + synergiaGet(TAG, endpoint) { text -> + val doc = Jsoup.parse(text) + + val messageElement = doc.select(".container-message tr")[0].child(1) + val detailsElement = messageElement.child(1) + val readElement = messageElement.children().last() + + val body = messageElement.select(".container-message-content").html() + + messageObject.apply { + this.body = body + + clearAttachments() + if (messageElement.children().size >= 5) { + messageElement.child(3).select("tr").forEachIndexed { i, attachment -> + if (i == 0) return@forEachIndexed // Skip the header + val filename = attachment.child(0).text().trim() + val attachmentId = "wiadomosci\\\\/pobierz_zalacznik\\\\/[0-9]+?\\\\/([0-9]+)\"".toRegex() + .find(attachment.select("img").attr("onclick"))?.get(1) + ?: return@forEachIndexed + addAttachment(attachmentId.toLong(), filename, -1) + } + } + } + + val messageRecipientList = mutableListOf() + + when (messageObject.type) { + Message.TYPE_RECEIVED -> { + val senderFullName = detailsElement.child(0).select(".left").text() + val senderGroupName = "\\[(.+?)]".toRegex().find(senderFullName)?.get(1)?.trim() + + data.teacherList.singleOrNull { it.id == messageObject.senderId }?.apply { + setTeacherType(when (senderGroupName) { + /* https://api.librus.pl/2.0/Messages/Role */ + "Pomoc techniczna Librus", "SuperAdministrator" -> Teacher.TYPE_SUPER_ADMIN + "Administrator szkoły" -> Teacher.TYPE_SCHOOL_ADMIN + "Dyrektor Szkoły" -> Teacher.TYPE_PRINCIPAL + "Nauczyciel" -> Teacher.TYPE_TEACHER + "Rodzic", "Opiekun" -> Teacher.TYPE_PARENT + "Sekretariat" -> Teacher.TYPE_SECRETARIAT + "Uczeń" -> Teacher.TYPE_STUDENT + "Pedagog/Psycholog szkolny" -> Teacher.TYPE_PEDAGOGUE + "Pracownik biblioteki" -> Teacher.TYPE_LIBRARIAN + "Inny specjalista" -> Teacher.TYPE_SPECIALIST + "Jednostka Nadrzędna" -> { + typeDescription = "Jednostka Nadrzędna" + Teacher.TYPE_OTHER + } + "Jednostka Samorządu Terytorialnego" -> { + typeDescription = "Jednostka Samorządu Terytorialnego" + Teacher.TYPE_OTHER + } + else -> Teacher.TYPE_OTHER + }) + } + + val readDateText = readElement.select(".left").text() + val readDate = when (readDateText.isNotNullNorEmpty()) { + true -> Date.fromIso(readDateText) + else -> 0 + } + + val messageRecipientObject = MessageRecipientFull( + profileId = profileId, + id = -1, + messageId = messageObject.id, + readDate = readDate + ) + + messageRecipientObject.fullName = profile.accountName + ?: profile.studentNameLong + + messageRecipientList.add(messageRecipientObject) + } + + Message.TYPE_SENT -> { + + readElement.select("tr").forEachIndexed { i, receiver -> + if (i == 0) return@forEachIndexed // Skip the header + + val receiverFullName = receiver.child(0).text() + val receiverName = receiverFullName.split('(')[0].swapFirstLastName() + + val teacher = data.teacherList.singleOrNull { it.fullName == receiverName } + val receiverId = teacher?.id ?: -1 + + val readDate = when (val readDateText = receiver.child(1).text().trim()) { + "NIE" -> 0 + else -> Date.fromIso(readDateText) + } + + val messageRecipientObject = MessageRecipientFull( + profileId = profileId, + id = receiverId, + messageId = messageObject.id, + readDate = readDate + ) + + messageRecipientObject.fullName = receiverName + + messageRecipientList.add(messageRecipientObject) + } + } + } + + if (!messageObject.seen) { + data.setSeenMetadataList.add(Metadata( + messageObject.profileId, + Metadata.TYPE_MESSAGE, + messageObject.id, + true, + true + )) + } + + messageObject.recipients = messageRecipientList + data.messageRecipientList.addAll(messageRecipientList) + + data.messageList.add(messageObject) + data.messageListReplace = true + + EventBus.getDefault().postSticky(MessageGetEvent(messageObject)) + onSuccess() + } + } ?: onSuccess() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt new file mode 100644 index 00000000..7ba18033 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt @@ -0,0 +1,116 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_NOT_IMPLEMENTED +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusSynergiaGetMessages(override val data: DataLibrus, + override val lastSync: Long?, + private val type: Int = Message.TYPE_RECEIVED, + archived: Boolean = false, + val onSuccess: (Int) -> Unit) : LibrusSynergia(data, lastSync) { + companion object { + const val TAG = "LibrusSynergiaGetMessages" + } + + init { + val endpoint = when (type) { + Message.TYPE_RECEIVED -> "wiadomosci/5" + Message.TYPE_SENT -> "wiadomosci/6" + else -> null + } + val endpointId = when (type) { + Message.TYPE_RECEIVED -> ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_RECEIVED + else -> ENDPOINT_LIBRUS_SYNERGIA_MESSAGES_SENT + } + + if (endpoint != null) { + synergiaGet(TAG, endpoint) { text -> + val doc = Jsoup.parse(text) + + fun getRecipientId(name: String): Long = data.teacherList.singleOrNull { + it.fullNameLastFirst == name + }?.id ?: { + val teacherObject = Teacher( + profileId, + -1 * Utils.crc16(name.swapFirstLastName().toByteArray()).toLong(), + name.splitName()?.second!!, + name.splitName()?.first!! + ) + data.teacherList.put(teacherObject.id, teacherObject) + teacherObject.id + }.invoke() + + doc.select(".decorated.stretch tbody > tr").forEach { messageElement -> + val url = messageElement.select("a").first().attr("href") + val id = Regexes.LIBRUS_MESSAGE_ID.find(url)?.get(1)?.toLong() ?: return@forEach + val subject = messageElement.child(3).text() + val sentDate = Date.fromIso(messageElement.child(4).text()) + val recipientName = messageElement.child(2).text().split('(')[0].fixName() + val recipientId = getRecipientId(recipientName) + val read = messageElement.child(2).attr("style").isNotEmpty() + + val senderId = when (type) { + Message.TYPE_RECEIVED -> recipientId + else -> null + } + + val receiverId = when (type) { + Message.TYPE_RECEIVED -> -1 + else -> recipientId + } + + val notified = when (type) { + Message.TYPE_SENT -> true + else -> profile?.empty ?: false + } + + val messageObject = Message( + profileId = profileId, + id = id, + type = type, + subject = subject, + body = null, + senderId = senderId, + addedDate = sentDate + ) + + val messageRecipientObject = MessageRecipient( + profileId, + receiverId, + -1, + if (read) 1 else 0, + id + ) + + messageObject.hasAttachments = !messageElement.child(1).select("img").isEmpty() + + data.messageList.add(messageObject) + data.messageRecipientList.add(messageRecipientObject) + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_MESSAGE, + id, + notified, + notified + )) + } + + when (type) { + Message.TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS) + Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, MainActivity.DRAWER_ITEM_MESSAGES) + } + onSuccess(endpointId) + } + } else { + data.error(TAG, ERROR_NOT_IMPLEMENTED) + onSuccess(endpointId) + } + } +} From 35f4f343426dc9f364a2f9f62e134e1fb3d18a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 9 May 2020 23:01:42 +0200 Subject: [PATCH 21/24] [API/Vulcan] Add syncing first semester. Disable counting releases in attendance. --- .../data/api/edziennik/vulcan/DataVulcan.kt | 9 +++ .../vulcan/data/api/VulcanApiAttendance.kt | 38 ++++++++++--- .../vulcan/data/api/VulcanApiEvents.kt | 55 +++++++++++++------ .../vulcan/data/api/VulcanApiGrades.kt | 35 ++++++++++-- .../vulcan/data/api/VulcanApiNotices.kt | 29 +++++++++- .../data/api/VulcanApiProposedGrades.kt | 45 +++++++++++---- .../data/api/VulcanApiUpdateSemester.kt | 1 + .../vulcan/firstlogin/VulcanFirstLogin.kt | 1 + .../edziennik/vulcan/login/VulcanLoginApi.kt | 5 ++ .../szczodrzynski/edziennik/data/db/AppDb.kt | 5 +- .../data/db/migration/Migration88.kt | 15 +++++ 11 files changed, 193 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration88.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt index d326ebd4..47b9ef23 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt @@ -124,6 +124,15 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app get() { mStudentSemesterId = mStudentSemesterId ?: profile?.getStudentData("studentSemesterId", 0); return mStudentSemesterId ?: 0 } set(value) { profile?.putStudentData("studentSemesterId", value) ?: return; mStudentSemesterId = value } + private var mSemester1Id: Int? = null + var semester1Id: Int + get() { mSemester1Id = mSemester1Id ?: profile?.getStudentData("semester1Id", 0); return mSemester1Id ?: 0 } + set(value) { profile?.putStudentData("semester1Id", value) ?: return; mSemester1Id = value } + private var mSemester2Id: Int? = null + var semester2Id: Int + get() { mSemester2Id = mSemester2Id ?: profile?.getStudentData("semester2Id", 0); return mSemester2Id ?: 0 } + set(value) { profile?.putStudentData("semester2Id", value) ?: return; mSemester2Id = value } + /** * ListaUczniow/OkresNumer, e.g. 1 or 2 */ diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt index b75da00f..2814a1cf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiAttendance.kt @@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi import pl.szczodrzynski.edziennik.data.db.entity.Attendance import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.utils.models.Date @@ -25,15 +26,38 @@ class VulcanApiAttendance(override val data: DataVulcan, data.db.attendanceTypeDao().getAllNow(profileId).toSparseArray(data.attendanceTypes) { it.id } } - val startDate: String = profile.getSemesterStart(profile.currentSemester).stringY_m_d - val endDate: String = profile.getSemesterEnd(profile.currentSemester).stringY_m_d + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getAttendance(profile, semesterId - 1, semesterNumber - 1) { + getAttendance(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getAttendance(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE) } + + private fun finish() { + data.setSyncNext(ENDPOINT_VULCAN_API_ATTENDANCE, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE) + } + + private fun getAttendance(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { + val startDate = profile.getSemesterStart(semesterNumber).stringY_m_d + val endDate = profile.getSemesterEnd(semesterNumber).stringY_m_d apiGet(TAG, VULCAN_API_ENDPOINT_ATTENDANCE, parameters = mapOf( "DataPoczatkowa" to startDate, "DataKoncowa" to endDate, "IdOddzial" to data.studentClassId, "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId + "IdOkresKlasyfikacyjny" to semesterId )) { json, _ -> json.getJsonObject("Data")?.getJsonArray("Frekwencje")?.forEach { attendanceEl -> val attendance = attendanceEl.asJsonObject @@ -47,7 +71,7 @@ class VulcanApiAttendance(override val data: DataVulcan, val lessonDate = Date.fromMillis(lessonDateMillis) val startTime = data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime - val lessonSemester = profile.dateToSemester(lessonDate) + val lessonSemester = semesterNumber val attendanceObject = Attendance( profileId = profileId, @@ -65,6 +89,7 @@ class VulcanApiAttendance(override val data: DataVulcan, addedDate = lessonDate.combineWith(startTime) ).also { it.lessonNumber = attendance.getInt("Numer") + it.isCounted = it.baseType != Attendance.TYPE_RELEASED } data.attendanceList.add(attendanceObject) @@ -79,8 +104,7 @@ class VulcanApiAttendance(override val data: DataVulcan, } } - data.setSyncNext(ENDPOINT_VULCAN_API_ATTENDANCE, SYNC_ALWAYS) - onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE) + onSuccess() } - } ?: onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE) } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt index 4366807a..6f045828 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.getBoolean import pl.szczodrzynski.edziennik.getJsonArray @@ -31,11 +32,43 @@ class VulcanApiEvents(override val data: DataVulcan, init { data.profile?.also { profile -> - val startDate: String = when (profile.empty) { - true -> profile.getSemesterStart(profile.currentSemester).stringY_m_d + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getEvents(profile, semesterId - 1, semesterNumber - 1) { + getEvents(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getEvents(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS) } + + private fun finish() { + when (isHomework) { + true -> { + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS) + } + false -> { + data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK)) + data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS) + } + } + onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS) + } + + private fun getEvents(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { + val startDate = when (profile.empty) { + true -> profile.getSemesterStart(semesterNumber).stringY_m_d else -> Date.getToday().stepForward(0, -1, 0).stringY_m_d } - val endDate: String = profile.getSemesterEnd(profile.currentSemester).stringY_m_d + val endDate = profile.getSemesterEnd(semesterNumber).stringY_m_d val endpoint = when (isHomework) { true -> VULCAN_API_ENDPOINT_HOMEWORK @@ -46,7 +79,7 @@ class VulcanApiEvents(override val data: DataVulcan, "DataKoncowa" to endDate, "IdOddzial" to data.studentClassId, "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId + "IdOkresKlasyfikacyjny" to semesterId )) { json, _ -> val events = json.getJsonArray("Data") @@ -94,17 +127,7 @@ class VulcanApiEvents(override val data: DataVulcan, )) } - when (isHomework) { - true -> { - data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) - data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS) - } - false -> { - data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK)) - data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS) - } - } - onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS) + onSuccess() } - } ?: onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS) } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt index 815bd83b..9a354ea5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiGrades.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import java.text.DecimalFormat import kotlin.math.roundToInt @@ -27,9 +28,33 @@ class VulcanApiGrades(override val data: DataVulcan, init { data.profile?.also { profile -> + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getGrades(profile, semesterId - 1, semesterNumber - 1) { + getGrades(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getGrades(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(ENDPOINT_VULCAN_API_GRADES) } + + private fun finish() { + data.toRemove.add(DataRemoveModel.Grades.semesterWithType(data.studentSemesterNumber, TYPE_NORMAL)) + data.setSyncNext(ENDPOINT_VULCAN_API_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_API_GRADES) + } + + private fun getGrades(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { apiGet(TAG, VULCAN_API_ENDPOINT_GRADES, parameters = mapOf( "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId + "IdOkresKlasyfikacyjny" to semesterId )) { json, _ -> val grades = json.getJsonArray("Data") @@ -99,7 +124,7 @@ class VulcanApiGrades(override val data: DataVulcan, category = category, description = finalDescription, comment = null, - semester = data.studentSemesterNumber, + semester = semesterNumber, teacherId = teacherId, subjectId = subjectId, addedDate = addedDate @@ -115,9 +140,7 @@ class VulcanApiGrades(override val data: DataVulcan, )) } - data.toRemove.add(DataRemoveModel.Grades.semesterWithType(data.studentSemesterNumber, Grade.TYPE_NORMAL)) - data.setSyncNext(ENDPOINT_VULCAN_API_GRADES, SYNC_ALWAYS) - onSuccess(ENDPOINT_VULCAN_API_GRADES) + onSuccess() } - } ?: onSuccess(ENDPOINT_VULCAN_API_GRADES) } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt index 56b4d918..45155a5b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiNotices.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.getJsonArray import pl.szczodrzynski.edziennik.getLong @@ -30,6 +31,29 @@ class VulcanApiNotices(override val data: DataVulcan, data.db.noticeTypeDao().getAllNow(profileId).toSparseArray(data.noticeTypes) { it.id } } + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getNotices(profile, semesterId - 1, semesterNumber - 1) { + getNotices(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getNotices(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(ENDPOINT_VULCAN_API_NOTICES) } + + private fun finish() { + data.setSyncNext(ENDPOINT_VULCAN_API_NOTICES, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_API_NOTICES) + } + + private fun getNotices(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { apiGet(TAG, VULCAN_API_ENDPOINT_NOTICES, parameters = mapOf( "IdUczen" to data.studentId, "IdOkresKlasyfikacyjny" to data.studentSemesterId @@ -67,8 +91,7 @@ class VulcanApiNotices(override val data: DataVulcan, )) } - data.setSyncNext(ENDPOINT_VULCAN_API_NOTICES, SYNC_ALWAYS) - onSuccess(ENDPOINT_VULCAN_API_NOTICES) + onSuccess() } - } ?: onSuccess(ENDPOINT_VULCAN_API_NOTICES) } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt index 771ee5c8..e897df92 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiProposedGrades.kt @@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.getJsonArray import pl.szczodrzynski.edziennik.getJsonObject import pl.szczodrzynski.edziennik.utils.Utils @@ -27,32 +28,54 @@ class VulcanApiProposedGrades(override val data: DataVulcan, init { data.profile?.also { profile -> + val semesterId = data.studentSemesterId + val semesterNumber = data.studentSemesterNumber + if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) { + getProposedGrades(profile, semesterId - 1, semesterNumber - 1) { + getProposedGrades(profile, semesterId, semesterNumber) { + finish() + } + } + } + else { + getProposedGrades(profile, semesterId, semesterNumber) { + finish() + } + } + + } ?: onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY) } + + private fun finish() { + data.setSyncNext(ENDPOINT_VULCAN_API_GRADES_SUMMARY, 6*HOUR) + onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY) + } + + private fun getProposedGrades(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) { apiGet(TAG, VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS, parameters = mapOf( "IdUczen" to data.studentId, - "IdOkresKlasyfikacyjny" to data.studentSemesterId + "IdOkresKlasyfikacyjny" to semesterId )) { json, _ -> val grades = json.getJsonObject("Data") grades.getJsonArray("OcenyPrzewidywane")?.let { - processGradeList(it, isFinal = false) + processGradeList(it, semesterNumber, isFinal = false) } grades.getJsonArray("OcenyKlasyfikacyjne")?.let { - processGradeList(it, isFinal = true) + processGradeList(it, semesterNumber, isFinal = true) } - data.setSyncNext(ENDPOINT_VULCAN_API_GRADES_SUMMARY, 6*HOUR) - onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY) + onSuccess() } - } ?: onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY) } + } - private fun processGradeList(grades: JsonArray, isFinal: Boolean) { - grades.asJsonObjectList()?.forEach { grade -> + private fun processGradeList(grades: JsonArray, semesterNumber: Int, isFinal: Boolean) { + grades.asJsonObjectList().forEach { grade -> val name = grade.get("Wpis").asString val value = Utils.getGradeValue(name) val subjectId = grade.get("IdPrzedmiot").asLong - val id = subjectId * -100 - data.studentSemesterNumber + val id = subjectId * -100 - semesterNumber val color = Utils.getVulcanGradeColor(name) @@ -60,7 +83,7 @@ class VulcanApiProposedGrades(override val data: DataVulcan, profileId = profileId, id = id, name = name, - type = if (data.studentSemesterNumber == 1) { + type = if (semesterNumber == 1) { if (isFinal) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER1_PROPOSED } else { if (isFinal) TYPE_SEMESTER2_FINAL else TYPE_SEMESTER2_PROPOSED @@ -71,7 +94,7 @@ class VulcanApiProposedGrades(override val data: DataVulcan, category = "", description = null, comment = null, - semester = data.studentSemesterNumber, + semester = semesterNumber, teacherId = -1, subjectId = subjectId ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiUpdateSemester.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiUpdateSemester.kt index 1f03484c..50fb178a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiUpdateSemester.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiUpdateSemester.kt @@ -60,6 +60,7 @@ class VulcanApiUpdateSemester(override val data: DataVulcan, data.studentClassId = studentClassId data.studentSemesterId = studentSemesterId data.studentSemesterNumber = studentSemesterNumber + data.profile.studentData["semester${studentSemesterNumber}Id"] = studentSemesterId data.currentSemesterEndDate = currentSemesterEndDate profile.studentClassName = studentClassName dateSemester1Start?.let { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt index 291cb37b..22f823b1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt @@ -93,6 +93,7 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { studentData["studentClassId"] = studentClassId studentData["studentSemesterId"] = studentSemesterId studentData["studentSemesterNumber"] = studentSemesterNumber + studentData["semester${studentSemesterNumber}Id"] = studentSemesterId studentData["schoolSymbol"] = schoolSymbol studentData["schoolName"] = schoolName studentData["currentSemesterEndDate"] = currentSemesterEndDate diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt index 4385b18a..6ff458da 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt @@ -29,6 +29,11 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) { } init { run { + if (data.studentSemesterNumber == 1 && data.semester1Id == 0) + data.semester1Id = data.studentSemesterNumber + if (data.studentSemesterNumber == 2 && data.semester2Id == 0) + data.semester2Id = data.studentSemesterNumber + if (data.profile != null && data.isApiLoginValid()) { onSuccess() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt index b92f0e4a..333967b0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt @@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.* LibrusLesson::class, TimetableManual::class, Metadata::class -], version = 87) +], version = 88) @TypeConverters( ConverterTime::class, ConverterDate::class, @@ -172,7 +172,8 @@ abstract class AppDb : RoomDatabase() { Migration84(), Migration85(), Migration86(), - Migration87() + Migration87(), + Migration88() ).allowMainThreadQueries().build() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration88.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration88.kt new file mode 100644 index 00000000..0dcd2f8c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration88.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-5-9. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration88 : Migration(87, 88) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("UPDATE endpointTimers SET endpointLastSync = 0 WHERE endpointId IN (1030, 1040, 1050, 1060, 1070, 1080);") + database.execSQL("UPDATE profiles SET empty = 1 WHERE loginStoreType = 4") + } +} From ef1cdd5b2013f914aac37f454ff9b3272186f162 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Sun, 10 May 2020 19:44:44 +0200 Subject: [PATCH 22/24] [API/Librus] Use Synergia message module when messages module login fails. --- .../data/api/edziennik/librus/DataLibrus.kt | 6 ++++++ .../edziennik/data/api/edziennik/librus/Librus.kt | 14 +++++++------- .../data/api/edziennik/librus/data/LibrusData.kt | 6 ++++-- .../data/synergia/LibrusSynergiaGetMessages.kt | 4 ++-- .../edziennik/librus/login/LibrusLoginMessages.kt | 6 +++++- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt index 80a85a9f..d4e834a6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt @@ -277,4 +277,10 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app var timetableNotPublic: Boolean get() { mTimetableNotPublic = mTimetableNotPublic ?: profile?.getStudentData("timetableNotPublic", false); return mTimetableNotPublic ?: false } set(value) { profile?.putStudentData("timetableNotPublic", value) ?: return; mTimetableNotPublic = value } + + /** + * Set to false when Recaptcha helper doesn't provide a working token. + * When it's set to false uses Synergia for messages. + */ + var messagesLoginSuccessful: Boolean = true } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt index a9a107b9..48f308f8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt @@ -9,6 +9,8 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusData import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api.LibrusApiAnnouncementMarkAsRead +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetAttachment +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetMessage import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetRecipientList import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesSendMessage import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.* @@ -86,10 +88,9 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va } override fun getMessage(message: MessageFull) { - login(LOGIN_METHOD_LIBRUS_SYNERGIA) { - LibrusSynergiaGetMessage(data, message) { - completed() - } + login(LOGIN_METHOD_LIBRUS_MESSAGES) { + if (data.messagesLoginSuccessful) LibrusMessagesGetMessage(data, message) { completed() } + else LibrusSynergiaGetMessage(data, message) { completed() } } } @@ -121,9 +122,8 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va when (owner) { is Message -> { login(LOGIN_METHOD_LIBRUS_SYNERGIA) { - LibrusSynergiaGetAttachment(data, owner, attachmentId, attachmentName) { - completed() - } + if (data.messagesLoginSuccessful) LibrusMessagesGetAttachment(data, owner, attachmentId, attachmentName) { completed() } + LibrusSynergiaGetAttachment(data, owner, attachmentId, attachmentName) { completed() } } } is EventFull -> { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt index 119c22b9..877d803b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt @@ -216,11 +216,13 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) { */ ENDPOINT_LIBRUS_MESSAGES_RECEIVED -> { data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) - LibrusMessagesGetList(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + if (data.messagesLoginSuccessful) LibrusMessagesGetList(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + else LibrusSynergiaGetMessages(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) } ENDPOINT_LIBRUS_MESSAGES_SENT -> { data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) - LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + if (data.messagesLoginSuccessful) LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + else LibrusSynergiaGetMessages(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) } else -> onSuccess(endpointId) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt index 7ba18033..ffc8133a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaGetMessages.kt @@ -54,7 +54,7 @@ class LibrusSynergiaGetMessages(override val data: DataLibrus, val sentDate = Date.fromIso(messageElement.child(4).text()) val recipientName = messageElement.child(2).text().split('(')[0].fixName() val recipientId = getRecipientId(recipientName) - val read = messageElement.child(2).attr("style").isNotEmpty() + val read = messageElement.child(2).attr("style").isNullOrBlank() val senderId = when (type) { Message.TYPE_RECEIVED -> recipientId @@ -68,7 +68,7 @@ class LibrusSynergiaGetMessages(override val data: DataLibrus, val notified = when (type) { Message.TYPE_SENT -> true - else -> profile?.empty ?: false + else -> read || profile?.empty ?: false } val messageObject = Message( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt index 94404232..4a994472 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt @@ -55,7 +55,11 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { } text?.contains("Niepoprawny login i/lub hasło.") == true -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text) text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text) - text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) + text?.contains("eAccessDeny") == true -> { + // data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) + data.messagesLoginSuccessful = false + onSuccess() + } text?.contains("OffLine") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text) text?.contains("error") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text) text?.contains("eVarWhitThisNameNotExists") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) From 8864bb2a5efa0ab4c9f684b49ab6f614cd3af4f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 11 May 2020 13:42:30 +0200 Subject: [PATCH 23/24] [UI] Add new app icon and splash logo. --- app/src/main/AndroidManifest.xml | 1 - app/src/main/res/drawable/face_1.jpg | Bin 0 -> 4930 bytes .../main/res/drawable/ic_badge_drawable.xml | 6 +- .../res/drawable/ic_launcher_foreground.xml | 106 ------------------ .../res/mipmap-anydpi-v26/ic_launcher.xml | 4 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2810 -> 3581 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 3948 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4068 -> 0 bytes app/src/main/res/mipmap-hdpi/ic_splash.png | Bin 10315 -> 15920 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1951 -> 2349 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 2511 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2679 -> 0 bytes app/src/main/res/mipmap-mdpi/ic_splash.png | Bin 6885 -> 10380 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 3599 -> 4957 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 5606 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 5645 -> 0 bytes app/src/main/res/mipmap-xhdpi/ic_splash.png | Bin 13767 -> 21262 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 5271 -> 7477 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 9515 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 8437 -> 0 bytes app/src/main/res/mipmap-xxhdpi/ic_splash.png | Bin 20087 -> 32780 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 6885 -> 10380 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 14206 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 11869 -> 0 bytes app/src/main/res/mipmap-xxxhdpi/ic_splash.png | Bin 26483 -> 45775 bytes .../res/values/ic_launcher_background.xml | 8 ++ 27 files changed, 13 insertions(+), 117 deletions(-) create mode 100644 app/src/main/res/drawable/face_1.jpg delete mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/ic_launcher_background.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc82fed3..1062001b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,7 +24,6 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" - android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme.Dark" android:usesCleartextTraffic="true" diff --git a/app/src/main/res/drawable/face_1.jpg b/app/src/main/res/drawable/face_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..77c899f973578d8c1713591eec55d54c16914f49 GIT binary patch literal 4930 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 HHYffU5n=dY literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_badge_drawable.xml b/app/src/main/res/drawable/ic_badge_drawable.xml index 4ac8eb15..6091ab7b 100644 --- a/app/src/main/res/drawable/ic_badge_drawable.xml +++ b/app/src/main/res/drawable/ic_badge_drawable.xml @@ -1,10 +1,10 @@ - \ No newline at end of file + android:drawable="@mipmap/ic_launcher_foreground" /> + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 1e056f8b..00000000 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 8678a9ed..036d09bc 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 8678a9ed..00000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index 19af488663ba7de9950b83bd4ba4add6060d1eb8..4d4e91f3fad6ce849f22dfe8fdd72485a821dd3b 100644 GIT binary patch delta 3520 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 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..714e2112dd1ca497ff54ab96cebbaef930955189 GIT binary patch literal 3948 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 literal 10315 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 X@jya0${~Zj_y>^wtRPV(rXTP>bP)Z1 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index 974e0572dcd3ecb41090ad74aab5ead987e7d164..4f7ceaf6775d05dec55bdcf749af1efdc5576d00 100644 GIT binary patch delta 2279 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 QfB*mh07*qoM6N<$f}PW`2><{9 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..10fce979b94d62d92560b6841ab6049e70303434 GIT binary patch literal 2511 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 lrq2=Q&?hO(Es#$jda`E;g}-W7tqRQGX$l>~gZC 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 r5hWYAdZFwf`g7b=At?mZAR#^u!xo diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index a8cd0957133e9f0fb80fd38c6d733e45fb3c5371..1977b6353438c6e02d7abd39c928c01f67291fef 100644 GIT binary patch delta 4908 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 N002ovPDHLkV1n7oi%9?g diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..9e37ac8b989d6b38cb289c03259c37785daae517 GIT binary patch literal 5606 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 z z%a#7KwWm^&{~DcG*OK>5d)l@s8^0W#*ycB=r9yW#2mf{2$=0{pz9dsTqq+t!SGu4O z6VPDhB9s;?#k#7M_!WL%rs*FbLohI-ad}a9HP1?}Qnpy?|E}3-v~UPo{M+=EYvhL! QTL1Q(vA($;<}MWZ-@)|Lv;Y7A literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 1a9a8476b3d54603549d079541dad5318849d44d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5645 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 diff --git a/app/src/main/res/mipmap-xhdpi/ic_splash.png b/app/src/main/res/mipmap-xhdpi/ic_splash.png index 05d3708c68c8228441c494b7ce27515bb385f22d..ec734e0c5040c5396b958572f66cc2a6e1d15fcd 100644 GIT binary patch literal 21262 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 Va>_rnz)x2o6$MRrm7GZj9$G^Yo?&+Di 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 W{)-HOaJ&Ej002ovPDHLk0$_qRUND~k delta 5224 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 id6SqR)>4uj{Qm)N*Wvw$eQ0O^0000jT69 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..fa889dd2bc372417d50654848abb2d78471931f7 GIT binary patch literal 9515 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index e473eb243506fcc4441dcf7d548655b0495ab71c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8437 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 er~f3fgtFb+zV-S2*QWqKE#(H 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 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 76d8cfa77c9d7e5f7d0bee4ba245a56f08a581e2..f8ec3f40f7269f259620fa2d6904e2051013cea2 100644 GIT binary patch literal 10380 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 r5hWYAdZFwf`g7b=At?mZAR#^u!xo diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..f8b05ce7dbdb86abaf4d8c878f3b660171e4b06d GIT binary patch literal 14206 zcmeIZhf|YV)GwTaR1p=Bq7hMwNN-Z4D^0re4gn-c2}ti?RKzGnR3LzWfb<$dlN#wA zq=zaXp#_i48B1 zl$`xhk^@iLwS{;$jNQ2?2a4v+}P);+Q@>p1=vGPMZw#UV`@6%O2 z38FYFes^V~*zTDqv;L=x|Iq@Nhv8IVAujV=@cs??bSMaPJHs?KDR4B; zY1n5h)!d~3jk2|OU7rxo8Vi+&g<3U~It|Yh$j`pEeqs&+eGo1vFe;2^h(*w!n#O)Y z&n8Vy-r@E1=M{@SH+^(HL8Ydt*m@Mn{ho)yVz#DORvP;I@!hZmc_k@%=%}7C@Qe6l^ zk;4lFQeeEOsheQ*4(xKnZXH#owXyha@6Sg!9L09V9`Wbt*|#_3 z8TflEYxgo)FXGmDxZvD*Kh;5?tM6;)I6QXK!YhJK#_Zhu^3`M2mg7uGNTfSjWA4*} zKuU_EmSR!tbWDpk-IbhsdIva5NlCMEz-)!LKp@*E4`|^B?t?L^Db5a0KNYs`euiVN zwf$5A2-9R1jOwV^-f{DHZ{BR1v30N)tTQfO!RvTOIbaiXXn8)espHVn`Lu{?!sw{`L92kkMpid#^Fdol9DZ%TI``7`r5rV)<%Vg*==~8Q{fGN zPL2D2IN}g&&d=trCNhG)&UNwL6QDVkuUmLcAF^q$-MgLd;dy#wsND-8t|L~jZdBQk zkaQ^_3^V=z0rAw=ZnfCYJM=tCYielk@PD|qRoqr8ii($dL(dVyc-0gJ&VHKH?3J=E zlz9D&wuY_L&Rj-xQCVYeRl23YvHx(M;L5=h$r!~p)>#CvKBp0{(jy9xLQDMU^|XH7 zRMqZUwz&pgye2;45A(d30fs8+K=u^LqC9k2BnTPuCU2R?u&+wJ~ zCw_?aKTr1M5D7de^641>W*J;?-X_$%8GU(1!6;;cu`a?COUB{zGE!gE#FlH|82Ccf zs=Dde22exYM@5X|x<8qN?TAss4%CK5#Kcy8N%FHR;CxeCoz~)buOZzwKhfgP8;pVn zxnG`07S`u7CEPTRkqlW&xcg z3F!8OgVn5kQ`y>=S{W{fF;mr>o4h(!JtAIrM=<$UfE7}aV9Y}W?%?nllHGa2Kb)NM zLlo8Nbp`YSpWy%fH4Uf5w?G^N?5>Z6-!8!!oNfP0vjAnx3P?D+#iQHU%B1CVmmlSu zZ*YA(U-7adEfQg$*u1p_>?;Yp#dKUQe5(T(zV*N?Qy-sK-#B*z7CQB#E`{~(Ky4GM zzW80UBnRCdbg<&)1BZBMD{iYa;BW-hq?*(&{08@9m%L;|a%`F?D&SmS5l`GO+ckA= zoB6>an(hHrb7ZCtY2(R>7`TFFMvMSh!&4<-*pM{aaG*z zop(s^O?7fSA$UDp@|7)G)K5StKjzbi+D6tM0&7_FgK-`e_~T$hicDASsV49CEV{5; z^@3SaG;Aq5WOP12jsf9#H!iIUF}w_{+y$<4)#S6&{QG)KIr8i;m?!n6xY-$9a!q1A zw2WT$XQ`BFNkGPr#W;2$RQoeQpw3VnL2qExHF>nX_S8#5V&Y?tTxeS1!AhnNbm4;! zbhv+a{hX6k+w%UxqJ+AdEf;iJv%vN3>&u`QTM7QXDrM9oW9Gl3MY_tj3WrRhB$`w+x&ho7dX zL~np^b;4#XxkNl_<(YW&@>B8XwcjBtXIAn45@n6kz}KoU+(Bi;7-xe(PZ&ju4d}(a zF7w_iE-w6Bk46^|*qi3YrHas4xipQCb7Oi>jyirY`P>|}m4a9p7zUJ8uoAFg59_&S z*V)xuTF*LxbxG>S;=hcw;8TJsuy%^_gqmBU+fo9SbX_IQmtjE%)YUgZ3Wp1^ajzpE z`!64lkn30_!qyVKDzMU-`B;hae5}0K@wtW@8ZO@V5wU!zriL{Ge*vzM%cxCGkiv3z ztu_^xOYLW;MwNnu_}|~KBUT7!GhA;f}&7vr41}km8;sg`JYBrF)F2Rt$hOB8&yPA9b_c8F55}c*8jG5Ik8JH_>GK~M zlYt(4VN05+z_5?nV~>@rIs~|~9-uO6ZepniC$jRP@q}3>-e6 z+dOQws`){;i}l7LL;%jqDTqwx2b+ZFwyOS9(kQwqCeR1t=I(swOm)@b_LA3;A_<7h zkhZ8TBx3kVO#eS13KT}2%ciNOceO?&&bSl7vi#jn)g8f-DMp>IDM9|?mH)2cQfn=* zdiC{%Ulo~&CAv-s7S4t-f2WA%sDUqG+-dWRUJf8hWc=kT!=Vj z`f;X?OW30#On23pX|Mv@w+$8ExwF|W~37wi4tQb4tA06vxp7O z*s{|~s{$u?_0wWl&_WB-JDk>GD6S=2JPy0RR5`ohWHrF zn_YM#QS{(XWa2P1-nUVuEb!J{@Uj;E8M3SyYS`z$-61vHDm!@F4UZxBw<8y82g_F3 zME(@SG7se7a%>`I5fp9bck0R%AGjEO8DV+R=-)oCeqkg4@PrBNuv- zkbS)_wsPeteD|lGwgy>j(9@=i-L2~f}0rLuqwzU>y5xVoUM?{OqkTw7j&2r)8-%`-yj`AMlC#NLZ zNauu>^1SSn65W>x#(cB~zR5Zqa~%bI^wI{=Mw%5?&Iwc%AzTy40Cg78sgZ*S{nc@w z9A_xj2ML?7kD@6bl$o&FhUE|$u_e}zBZwYz<9j{U68AM=Ia4zaGGhEk-yAjtHaNNz zRCR|}q?-6A*o(_O*yfms)~+BfKSie>bX0_jp@;>DHBYUG*!Z>3Y^0k@kn_>Juei~K ziSyC26nNEKqCB`N0sGX>GYN_d1Z-7NoC>X&7{ji6O)TCxjnV~y;Ia9btou~dE?-zI zRIzX$zYH^|E*%N4W1|XNIx3T4vS1+k!gOn_uwB6Hd4xidUF)lNCE>$Gk4ff>_Cb_<^rS{zwJv140QZ-PEz~YP zgqxysjc?SrTu2ufv@8x96OC@?$?Ert2fCQn)QySdKHKkGmSov@-y_%+Z+bt5lMVF@ z!E$(Hc%^0d)5<0%Z%O>C#UBNjEPs+DQ(@9p%gukTnmnM?Tx>|Z2hWI-))AegDo{a@ zBqhh&9S(I0X9&1{9_U_`huhaYb|=eR=yF;l(cyAS|Nc%@ z%BZ`PRq^2JE0On{rN^F6PaM9)nAnD&oYa@+S05sim@FZAZA{?b3tS!%#Z~{>(UUCe z;o%)#ftDt3kDhNcsn;ZI=EEho-d&4_K1no$5ngjXfZd$xKqzjYU7zCn&$J599Kpt- zdy*2A-xWwAh(hckXG}u8J+@Y#YFpWoBpC>k{k0#IlFx%`Q%qV?UQc=yu}TY$9?pek z5odqfI#|VQT~z;(>g|XSw6>Vm?23=0A9))E?Rk3{+S88i8K;U*qCuZHiu12K*IOT! z9h|KF^S`4azdF}o1t^d4!V%lx5w5Q_%a=dMmG2+K@Z1)#i}a?4vTSVT2qSnz887LTF0aX6NaBHFc{WoQk0%3xW(%?vaFM zv4Kw`Nb2q^<=~zu!NtTn^9E6h-3?KS%QxS$>;e(peAk0r@8HQ*!>CSAHZYhmp`mQ4 zuQgqO`|fR&@!CvrfM2?6m6gTR5Y?!0fhU9lj;Z36I~ReQdNWhu2Jz*M;ck6GmAe!O(C+1|lY|J1f3RJ;d2Q&;Y1(}Ez@R3@x-Wq;o? zC=wK@OO*%n`ltzOpPAlaC0EXcg&{B1sc8D8|e+Cz`t zB|uN7Ejj6oX=B!=U|qp2vBi5okCuZ8Cx(hl(C-DCAhWtz?XD07dln;2N~{swe&Sc{$fD815Qz@=4w(eSZVhV=LDA7UAOq7(9=!#Bqo;2HDh?QqlbC}N^BQG_8#$RLxAiYkfA=IxH9=&TxD4| z5GTNVdt24ImHiY(Wa+~FUZGv1%YG+9mEAt9^4E}k52f=b%`P;hA*oS>dAl@jovx>fZ7t9bBW{UFP$(rpP!t)%zC(xzwLF z)iSU2@Nu9eP6zmQcR2>Bi?+v??ZK|Qeg2VBZFsY7E4{7py1+_o1uO|$<(^?$7f^nH zOLFV(jOCr`6LTJ^Y9rJ}X{gv9-aM2!v^Wep{Bl@(u-KH=6BBc~Lm zgk(g`Z`>)F>kT|$I^}^s%3Y)4vhv#+&~#oKFUNHWHr(H+CB`MlK)+oVe)i%hcZP5V z%=OK5!O^hY?Ilk#!OkcdRz?_OR>O>1*GSv*N(<%`@7(%E(c(V?vsIstvBoTwK}*yw zxF2_fl&#+K8-uQzNRf$)>Ori;(@C>Tn~<&b%}$Jokp~*@Z(!a}%=$$)cr7LG>0LZ( z4clvLxcxxrXyAaKpSH-nvX{@ge)3Z2`gKqHHb$K7QRR`leH(gmJ^;uq{pti}J#BZl zT`c@kkIs#BI2kyHzr9A?o$4O>ht1k5vJM+xc?J2={<0pNJIb~hqyTYb7~6S)u@Fxe z6g~|?R(@elK@9UC5m-Oo+a}A%sTbXz48S zIJ4>bO6_|6EVWA_!&t;C9SFhY7^!;!1kW^t45m}M)N+*oIEW%+blc6u%S{mz1?rG z)9n>Fp+S37zPQ3nx5TbKC$j>~&at^heDyp*s!k4TuCwgQa#$PI8>ak}L**iV0W)CZ zPLlKz^SOUhRORxEOAzz6K`8?cxZ{iD-_Uy?s%(r+~H?Z=luHQ6O^wh#ch+VkT+a`tt zypY*_gaFUi{f|E8lNE zmVS?|xa$a4Q=p3(SXk^!{`3KZ>kSyT=in){>Ut{`ewX%c0BU=>Fn?-scyTwL*0;?> zMP`g_cPm${SmPmtE9~(S;3^6CN`7t4LIIj9C%krL?pr2+}Oc6k5`+GGsocuP)aJ!k+n9j$`m8DB?$QXi{V$lZ24DOHinLFdEyM zVWTo=>7)K^ltJ@6P2@J0%JB7Q^H! zQFFCs(C?9bEXesj>?8ZIukWY$#cHeZK!MusLQJ4}~v0troDk?Zt| z-V|Rss<{=MoS!fX<6AfA;VVV;x4w+}4y}*$7q?pd;EeNW7xr}Nc1pFgf>{sO4ix1j zINAkF*4K2?e9hF)|F`$CbsiE0>dsx2A*r*b_3epjO(oJr$p}6;J)t~ zn&RPc$Y$3R?7C9Vw1fzRecQsa$SSs8$%7_-k;L6At z{(LKQ61dn!z(t;fAej_lJsNj1_KW9QUheBC9shYWyYDMR9zL(2gg3s#^EQ|kwRez? zYKvZML`kdTZklQJ=<0+df92B9eqIb}lq3uDZUhOWWsLk9D*gsF(U06RFOTyXN zg5`ov^7FCrjjf+^DGREqvYf%9w*3kcyn`&#_vzoq{zkct8tr`t5{Mn& zvN>`Ff_bb!a-l(BEKnVGE=ggZxNRnV09&ebEZdF3)BiXrl~+&QIx;IcRo?AsuLt8t zPYH)LwmXD0XZ71c%HArpFZiyFUBTWfIYE*6U@qf-kVZicgVd9rWSK%vT6#0AZ~lhb znrZW`+d2fV6Q&C@eMo(i1+*bVYSIc>4bol3gNvF}EUF?O&jLY)u|qCfYCb>W=wU*r zAPVz240##OUDWXP)4`vnE0cT$RqvBW`;N`iR)5%1X%c|_`I`6H>B#MpZaQQWMi=;#5+oS;y#n7I_!dU*PJ^K{FEO3kkatcI_dt{dlb-h7wAYeZ$r6+3MrG!h2ok0!6Ir?yxx9)-3omWqMb7tn_*gO6VmDq(nH(4;j>Mg}p6H`jfn64#~ z7nn{>NX$g;HTB;}k8*5-n+}5W?($=~>Q`@|PZQO+8S1P0g{6#&H(LxR<9N;@FF*x}wNRh#D~VT5 z)~&N2`Ab*zvB;bR3IT~|!oe&5g5tSo$&6j3>aD8oOZdPP ztlXbAHENe|u99cwdH4tumC3UGv@*(qGR0KUm_mbV(t1~sE2f0P0Ar&3I9I??mCvB3 zRBJ`{xL68iNdCA!J!D%cB4lCj0YRRiNNBrs4LK*Na{a|6`Z2}5)1P1bC!P<^6`^ni zDfC)hfp@V;i~H*-Pg7GNW?rmzi8I0j1-G7STEH!_L;+9S8oTEETl4tU_q>7W{DHN+ zM?eZ7ZccIYV=d+lBcn;Ty;A02wA3hmYD-I{OCwb$)w3yQzbjlDqWFpFKOw^>?x`Q; zOyND9RLlPra!9AqUgtTH%Lu#TyAtX!y<55(eVqq5$h_J3_`-*WB!N|ZsZ+mx=Iz}d z#KDaFSHENu+WBlw)KvCe`KV8bqg16q%PM#D8A+A0+`XWuP)2JxWn~>)7}$dd->n7 zFp8h9dzX1on0HUvwP_sK579!tT4Qf}UD55DpGS+tSjYNCbNlgK{H)siiNPj%@Z5UC zjl9ruMZ9=GPkF|;{@M*|NLzJC`pH~pNENaa)}Vi?FK-aq4LOTdKB$od{{E4V|9ZbA zSlZ`8H#@afxGEz9VwcGHhC*1nf63hY5y`4|DiJC>Oaoav;(u#aacj`i^=i#2!{A!| z59lgphqBvRg|AE+EkQk4M>eWeCzVMfpdnfY&y-DwG zj_l`RCBDTB=}a=Q2m3EGWmxCBOl%CD4qX{$6*Yl`JZg((b_a>5L4qA>9(L03MGFYf z&BCE6M~p=e`R>38^?m#_<-yNg@uhVSHD^x5#w>$os5$9O+|ead@CYSK=9^aPLH$5N zEGS9}m2U$oBU6jx%FJPbTq}Yr5=HoX zM4kqGy(zm;3o1eq0PZ0&#@h1Fp*Eh+x!rq!)gE`{O?FUp^G-_$dN|~Aa0;77IJ~>* z8CsCk2uI?~%$5ek`mt{?w)~vy)Er*-_=C5R+Oq}Cw8`Bg(QR=V&AT;wql^8ESbS#B zkw!R5N|rg#BH002$=r{rza%zx<+^XOx)g^Yaip~;v2lG z1WUZ$VnoydT$KD0Ljj}~mXOUjI$%T?M^+N9cLTP&HfbPE1(udbilkom-T-S^A^2F> zN<8S1={{$NF{DO`BpL>`QhVP>-<5wldFm5_{(bNDjrC9^RkXX64mz+Y+c9=@ohB9QvEiffCV;gr zb$V2Ok>PW^T()5G9Mqb!z~XEr0Qk)UZ;3^QYP;w$#S^lJyhFt+Twg1VU|o=b+1O3O zBXsCq#gOZvvvu~U+n0%{*lZ7?Y5cU#M*iO3;KHX^rnB-dcb*8zKR|te)`Smn(1i%` zN!J_kC57501;s>ON@X>dUZ+F5KXgN{eL}2jZyx!;5bnZTjC-|FgPYE+mUyd;_Zka_ zXn`{s`@|zG#5I+yfXx}`yohRI4g>5DgiMl%11#bPr*h9b4(3pmtju~sO1Iav`Xe*h z$<`dtp7G82gmU`RrL(iS_16y3gPQ@#RvUd)21QBUWO0`=?7??=0B2Yy(H}Ys_JGht z1G34%_$BgyZDJPTvRd`0K$C9#z7*g6o~&zhLjgJlCw2iLpSbwzT=Th#SpIP*bFk!$ z0VaU*Z#toF4Eb3~KabGV2s%KQQYBkfe0}{5I~0eqK^>(1P1vQTQ(?JZo;Jtk)Lu1d zr7U>(-z2~1LOqUgL==DMmPuQqY<=C>0G#nMeU57|V7_K7^*anBq5^rx6QKh5xd9m? zH}J7?Z+qF5ctKI?XMuN_n%6tRLifUA1q<(q7MaYc8(C5EWsshs8kP#LUg@T9F45l| zUA?A!634}f)#hw9;wPKP?bhn@;mK8EW81cm6D-4A;um?@$NE-eLOr`mou}mGngNcn zA^BZ}4EIkT9^uaQMYn8`E<%&4NTB>#C;=>c;-S@S#e27mNQ$-FS#EP{ljLzfkc;GZ zf;C2P;p60Tv;d6jxcR#Hp}TE6L;i?WvVQhCu3MU3)F?}{S7VE7epbn;BZ5CV~b9kd1?xewnPTcw!w?H zg<5__sDm>3O2UthJIa>ivLNREM=*;EhA}14&R+*Bf1?8;Jk49%7)<(Vro=Z7M{Z3U z$pv7?%PR?WgOe|OXJs#RYC%v+jNItQX)W*CN0oRbEK?sC0^pPU%!QcGl3q~5+qdL9_<|-w-f*oXuv`fJEDS&obThq7-kgU-UpD`2zdD5kDpkZf z%TGi(>6AOmz;i?QgE@_?w~i85eRU*!h);+p5~>1sz^h*j&?8w-ckP}_URG)jY5Dqc zjEVLqd6{g83{%vUuI(tM5hcoe{cee7lmRIYm}(_wt_B*ScqCUz@T-e^@kHIj&oiUP z^-P7<(C?UQV&^G?5a}CC=@ZC1YmrC)+&;h2{TnEE(~Vsj>=UlJ>+QZLckE*l2sA73 zUZ92FDS0ayqkHlEP>Un`N-E5r{4@jceS2;kR}AFE`BJ}e;b+|!C$AjLViUT7B}olr zHsKmQ$$21C4AaZIgG-(oDzZXDt}M;jvh7w78&m%$<@5 zCS)pb+Duu1C(WwqkoSkNd)gnBRB`04RvSo+rdm!-_KWpR=16k=Z8A7-qu6^LT-W&! zLQ#&n7Ry|CzNKg`mb+G>@2Nc&&3CSOY&P68Q&4iUH+cicy2wEDw}E1Gb?0!Rde~#( zPRT2?znx~cO9mK`;$Mg8e#NphZdLd87P#&-6G9h9g(NJW3{D=`B;rzO)6VLbXAmeT z99szsVj}hVbJm_lOAS~j5#uGD({?(3FBF$)|F9?TepB_q%aR<|x{|o>Yw2bR$>a<`@u#FR^@MK{6%#CdgWWbulG zGsP{t5gFeI)BOI1jMo)y{Vc7-G0~+qcd`l`Hltgj@237eBjx7Kld$3BG_;h}!#)J}Ts<0}ZHb$zIC?VJ8W-GQN$d=kj(vDK z!69?B6?ZxILz<_V8RMl+jmGLD5&0F|X^q3{<{hcJlhfKvrBKGK_sLy>(hS3uE0%lz9u}t8qW6ubSk6&E)=Y0oW`q=8~yJ zQ=XeCo_gu^EzhLOyD2L^J;T$w2$yI6hS4dmSGZwLV=L3ksIH1WM@7Zh;Mu9ZxO_}w zS9tm4cb=ZOlMq*{6THS^#k)T(_4Xz%WB0KfJ^H_I%UN%$F^jFk4gZRoc zM`74d=R~&w0IOP06*UO-#uXF&UMj-&;S!>TX9Bj9`jBKW-7dLCmc>6=j)nNG?md_- zoXKc;<*79xhi(K=_@Nqr>{BrZDbu@SYObSGbDgfMFymr4K7G0`J|FXWE+pA^Elxmv zChLRRvwW9XY5{Ss8F3IaNRCKOgm&J zxu5`{qQb{e6pQe$Da>=&QGB`_%_qWg%hEy;Z+F9GT=pqNA_esOzoGdpX?__=Mq{6b zu=S)W=s!#|doQt4CC31MbyD<&PI-k$!sM!gkn%_GJ{Ui-J>lc4ipcRWu=*o|oGzXu zg|7b1Rr6VFD}R!Vp6>^bNYF$jqB2p1m_1esJ5OJfAVAe7m;^mr&mTDRc~GDv=s{cq zrvCc)v<0jseWC7n+qxRpWiE%Gs4_cSVC@-Cr?@tKU^_YuX&xf?0K)k6r zJph`;6p9ew^O{ZQEcL*llP^_CKk9WEXH-Mn|f)8i6ejFEHvdtDX|IkMMx ze`=k7hKRY$19y}cI$vhS zyEQ-6``v8)r^3kYkGK7H!4BaL)^*w6lReArgCU?MnxAT&BNIOW3Z_tXL|+kbQgH=g2~xVq--x&*Th z-H`p+B=Hwl?rESK#BVyk5aBdhVMABp313 z0zj0}*CQ&S24OOTd*{Qdt@TPG#zckad2f)lE&0`H7aKnSurex0 z*S^SXpT!uJ7AAv})BLp&_pTHE#L)#Z(#zIj0Ok?#P{m&Ki}b4t=PiNRq#gA*0yGhUW?7?&6d-pX@Y_hM_Y7~1}U9}2j@xytKA50 z`BQH6I8$HDQ_(n?qDxdF*;-T{tSic$+*iFlN0OlY=5&%}y1T1S&ftNWTS8fRdXj8HSeViM<3%svobgOc&pL@Cy@P;M!+)V5juB4d z=yuXyotMP&0O5VPuh|u!xJbIQ=h-AOP=IG$49{e|9$?Y8D#=njx=i3vUC6aK?}7Li zWK0ltK+7(!G|sS_b;3Z**pF^7qJt>``b0|N?wuBDmhAap_XAPEj=x2K|HIUBU{1-? zG(S&UB-AO%uE-^IwK-$ksG9hRK9My!fx<;lIpPZW>p zrq>u&=q1)K6a1w+O!Ag!rQ1d|-;m32+bKUK*J9dq?;ki~g&#Th`;kK6YWKVMIP#;NGN$JYxUXgUPZTrQB z4?+@nPCI>Kt^1D-Hdu~jI!^e{I;Qfj48LDdC44(HyP9nU9BPzC(!nO?4%-H<0R>*i zM?`FEdEq0LtIP^M`rI0)^z_X?Zctvr%&Uq!3EjI&N^+rHCB$`}gPi=O0tAF@M_jr@ z3dev-V^YU*`hF=~rMz?-e8BnY!o%A_FI--~zI^tTX;&Ih>7^ur|MQfa!|R-e`s1bB zl%(Vz%5U~4JUkm@p4$OAzW_hzR7;H5J^Rc3-;uFPms!f0CC{m{ zhlf`wQk=2m%j9`=e&M1-#X@sk;qU7t5$t@&0GZ5JtAY zYQ%ZSyt?u~3{OqPay}?-<ENDMiCj$0vHqG?g>;)s(fBiXK5;{x6*})r|lE literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 35bb174e4eae9ba897afe4d64c135760519fe441..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11869 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 literal 26483 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 BR5Snp diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..d75968c4 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,8 @@ + + + + + #0C92D6 + From 15102fe818e2ba507f131e861f682c302e2c711f Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Mon, 11 May 2020 18:14:15 +0200 Subject: [PATCH 24/24] [Hotfix] Suppress ConvertSecondaryConstructorToPrimary in LibrusLoginApi. --- .../edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt index fe155abe..7151cf29 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt @@ -18,6 +18,7 @@ import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection.* +@Suppress("ConvertSecondaryConstructorToPrimary") class LibrusLoginApi { companion object { private const val TAG = "LoginLibrusApi"