From ad9a2711c46fbb86eca6cbe5f120441c42d13891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 19 Dec 2021 22:46:41 +0100 Subject: [PATCH 01/30] New Crowdin updates (#1687) --- app/src/main/res/values-cs/strings.xml | 12 ++++++------ app/src/main/res/values-de/strings.xml | 4 ++-- app/src/main/res/values-pl/strings.xml | 8 ++++---- app/src/main/res/values-ru/strings.xml | 16 ++++++++-------- app/src/main/res/values-sk/strings.xml | 10 +++++----- app/src/main/res/values-uk/strings.xml | 16 ++++++++-------- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 93ea10a11..8e49d4f1c 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -253,10 +253,10 @@ Nové zkoušky - Máte %d novou zkoušku - Máte %d nové zkoušky - Máte %d nových zkoušek - Máte %d nových zkoušek + %d nová zkouška + %d nové zkoušky + %d nových zkoušek + %d nových zkoušek %d zkouška @@ -750,8 +750,8 @@ Nelze se připojit ke deníku. Servery mohou být přetíženy. Prosím zkuste to znovu později Načítání dat se nezdařilo. Prosím zkuste to znovu později Je vyžadována změna hesla pro deník - Probíhá údržba UONET+ deník. Zkuste to později znovu - Neznámá chyba denika UONET+. Prosím zkuste to znovu později + Probíhá údržba deníku UONET+. Zkuste to později znovu + Neznámá chyba deniku UONET+. Prosím zkuste to znovu později Neznámá chyba aplikace. Prosím zkuste to znovu později Vyskytla se neočekávaná chyba Funkce je deaktivována přes vaší školou diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f81724a21..83bbe307f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -223,8 +223,8 @@ Neue prüfungen - Du hast %d neue Prüfung - Du hast %d neue Prüfungen + %d new exam + %d new exams %d prüfung diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 93bdd5354..17fcb0884 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -253,10 +253,10 @@ Nowe sprawdziany - Masz %d nowy sprawdzian - Masz %d nowe sprawdziany - Masz %d nowych sprawdzianów - Masz %d nowych sprawdzianów + %d nowy sprawdzian + %d nowe sprawdziany + %d nowych sprawdzianów + %d nowych sprawdzianów %d sprawdzian diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 33ec21826..e09e6d7da 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -253,10 +253,10 @@ Новые экзамены - Вы получили %d новый экзамен - Вы получили %d новый экзамен - Вы получили %d новый экзамен - Вы получили %d новых экзаменов + %d new exam + %d new exams + %d new exams + %d new exams %d экзамен @@ -671,9 +671,9 @@ Записывать официальные уведомления Показывать push-уведомления С помощью этой функции вы можете получить замену push-уведомлений, как в официальном приложении. Все, что вам нужно сделать, это разрешить Wulkanowy получать все уведомления в настройках системы.\n\nКак это работает?\nКогда вы получаете уведомление в Dziennik VULCAN, Wulkanowy будет уведомлен (это требует дополнительных прав) и запустит синхронизацию, чтобы отправить свое уведомление.\n\nТОЛЬКО ДЛЯ ПОЛЬЗОВАТЕЛЯ - Upcoming lesson notifications - You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature. - Go to settings + Показывать уведомления о будущих уроках + Вы должны разрешить приложению Wulkanowy установить будильник и напоминания в настройках системы, чтобы использовать эту функцию. + Перейти к настройкам Синхронизация Автоматическая синхронизация Приостановить синхронизации во время каникул @@ -695,7 +695,7 @@ Согласен Политика конфиденциальности Объявление загружается - Thank you for your support, come back later for more ads + Спасибо за вашу поддержку, возвращайтесь позже для дополнительной рекламы Расширенные Внешний вид & Поведение Уведомления diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 77e402add..584a923d5 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -253,10 +253,10 @@ Nové skúšky - Máte %d novú skúšku - Máte %d nové skúšky - Máte %d nových skúšok - Máte %d nových skúšok + %d nová skúška + %d nové skúšky + %d nových skúšok + %d nových skúšok %d skúška @@ -750,7 +750,7 @@ Nedá sa pripojiť ku denníku. Servery môžu byť preťažené. Prosím skúste to znova neskôr Načítanie údajov zlyhalo. Skúste neskôr prosím Je vyžadovaná zmena hesla pre denník - Prebieha údržba UONET+ denník. Skúste to neskôr znova + Prebieha údržba denníka UONET+. Skúste to neskôr znova Neznáma chyba dennika UONET+. Prosím skúste to znova neskôr Neznáma chyba aplikácie. Prosím skúste to znova neskôr Vyskytla sa neočakávaná chyba diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 8543a4708..5d24fedf2 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -253,10 +253,10 @@ Нові іспити - Ви отримали %d новий іспит - Ви отримали %d новий іспит - Ви отримали %d новий іспит - Ви отримали %d нових іспитів + %d new exam + %d new exams + %d new exams + %d new exams %d екзамен @@ -671,9 +671,9 @@ Захоплювати офіційні сповіщення програм Показувати push-повідомлення За допомогою цієї функції ви можете отримати заміну push -повідомлень, як у офіційному додатку. Все, що вам потрібно зробити, це дозволити Wulkanowy отримувати всі сповіщення у налаштуваннях вашої системи. \ N \ nЯк це працює? \ NКоли ви отримаєте сповіщення у Dziennik VULCAN, Wulkanowy отримає сповіщення (для цього призначені ці додаткові дозволи) і запустить синхронізація, яка може надсилати власне сповіщення. \ n \ n ТІЛЬКИ ДЛЯ РОЗШИРЕНИХ КОРИСТУВАЧІВ - Upcoming lesson notifications - You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature. - Go to settings + Показувати повідомлення о наступних уроках + Ви повинні дозволити Wulkanowy встановити будильник та нагадування у налаштуваннях вашої системи для використання цієї функції. + Перейти до налаштувань Синхронізація Автоматична синхронізація Призупинено на час канікул @@ -695,7 +695,7 @@ Погоджуюсь Політика конфіденційності Реклама завантажується - Thank you for your support, come back later for more ads + Дякуємо за вашу підтримку, повертайтеся пізніше для більшої кількості оголошень Додатково Вигляд & Поведінка Повідомлення From cc22985dc5349d36ef40388c7062efea6865b69a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 20:22:56 +0000 Subject: [PATCH 02/30] Bump firebase-bom from 29.0.2 to 29.0.3 (#1728) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ea74cd562..23cd666ed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -229,7 +229,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'com.fredporciuncula:flow-preferences:1.6.0' - playImplementation platform('com.google.firebase:firebase-bom:29.0.2') + playImplementation platform('com.google.firebase:firebase-bom:29.0.3') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 094df212b411699bb786460c4447c4d3ddeeb362 Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Tue, 21 Dec 2021 00:36:59 +0100 Subject: [PATCH 03/30] Add additional lessons feature (#1550) --- app/build.gradle | 2 +- .../45.json | 2430 +++++++++++++++++ app/src/main/AndroidManifest.xml | 1 - .../github/wulkanowy/data/db/AppDatabase.kt | 8 +- .../data/db/dao/TimetableAdditionalDao.kt | 11 +- .../wulkanowy/data/db/entities/Semester.kt | 1 - .../data/db/entities/TimetableAdditional.kt | 7 + .../data/repositories/TimetableRepository.kt | 13 +- .../modules/attendance/AttendanceFragment.kt | 4 +- .../modules/homework/add/HomeworkAddDialog.kt | 17 +- .../history/LuckyNumberHistoryFragment.kt | 4 +- .../ui/modules/timetable/TimetableFragment.kt | 8 +- .../additional/AdditionalLessonsAdapter.kt | 9 +- .../additional/AdditionalLessonsFragment.kt | 50 +- .../additional/AdditionalLessonsPresenter.kt | 38 +- .../additional/AdditionalLessonsView.kt | 6 + .../add/AdditionalLessonAddDialog.kt | 188 ++ .../add/AdditionalLessonAddPresenter.kt | 166 ++ .../additional/add/AdditionalLessonAddView.kt | 30 + .../completed/CompletedLessonsFragment.kt | 21 +- .../github/wulkanowy/utils/TimeExtension.kt | 55 +- app/src/main/res/drawable/ic_all_clock.xml | 12 + app/src/main/res/drawable/ic_calendat_all.xml | 10 + .../main/res/layout/dialog_additional_add.xml | 156 ++ app/src/main/res/layout/dialog_grade.xml | 1 - .../main/res/layout/dialog_homework_add.xml | 20 +- .../layout/fragment_timetable_additional.xml | 14 + .../res/layout/item_timetable_additional.xml | 18 +- app/src/main/res/values/strings.xml | 11 + build.gradle | 2 +- 30 files changed, 3222 insertions(+), 91 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/45.json create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt create mode 100644 app/src/main/res/drawable/ic_all_clock.xml create mode 100644 app/src/main/res/drawable/ic_calendat_all.xml create mode 100644 app/src/main/res/layout/dialog_additional_add.xml diff --git a/app/build.gradle b/app/build.gradle index 23cd666ed..1ff8b9268 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -167,7 +167,7 @@ huaweiPublish { ext { work_manager = "2.7.1" android_hilt = "1.0.0" - room = "2.3.0" + room = "2.4.0" chucker = "3.5.2" mockk = "1.12.1" coroutines = "1.5.2" diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/45.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/45.json new file mode 100644 index 000000000..57f3d431d --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/45.json @@ -0,0 +1,2430 @@ +{ + "formatVersion": 1, + "database": { + "version": 45, + "identityHash": "f310243440ca00cbc35e62ebaca5c7d8", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f310243440ca00cbc35e62ebaca5c7d8')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index de4a80b06..72fee08a7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -114,7 +114,6 @@ - { @Query("SELECT * FROM TimetableAdditional WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> + fun loadAll( + diaryId: Int, + studentId: Int, + from: LocalDate, + end: LocalDate + ): Flow> + + @Query("DELETE FROM TimetableAdditional WHERE repeat_id = :repeatId") + suspend fun deleteAllByRepeatId(repeatId: UUID) } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt index 3b1f0add5..3dd7ee0cb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt @@ -42,7 +42,6 @@ data class Semester( @PrimaryKey(autoGenerate = true) var id: Long = 0 - @ColumnInfo(name = "is_current") var current: Boolean = false } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt index c1f1365f9..db32de874 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt @@ -6,6 +6,7 @@ import androidx.room.PrimaryKey import java.io.Serializable import java.time.LocalDate import java.time.LocalDateTime +import java.util.UUID @Entity(tableName = "TimetableAdditional") data class TimetableAdditional( @@ -27,4 +28,10 @@ data class TimetableAdditional( @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @ColumnInfo(name = "repeat_id", defaultValue = "NULL") + var repeatId: UUID? = null + + @ColumnInfo(name = "is_added_by_user", defaultValue = "0") + var isAddedByUser: Boolean = false } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 8be621122..1f7bb1cff 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -152,7 +152,8 @@ class TimetableRepository @Inject constructor( old: List, new: List ) { - timetableAdditionalDb.deleteAll(old uniqueSubtract new) + val oldFiltered = old.filter { !it.isAddedByUser } + timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new) timetableAdditionalDb.insertAll(new uniqueSubtract old) } @@ -160,4 +161,14 @@ class TimetableRepository @Inject constructor( timetableHeaderDb.deleteAll(old uniqueSubtract new) timetableHeaderDb.insertAll(new uniqueSubtract old) } + + suspend fun saveAdditionalList(additionalList: List) = + timetableAdditionalDb.insertAll(additionalList) + + suspend fun deleteAdditional(additional: TimetableAdditional, deleteSeries: Boolean) = + if (deleteSeries) { + timetableAdditionalDb.deleteAllByRepeatId(additional.repeatId!!) + } else { + timetableAdditionalDb.deleteAll(listOf(additional)) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index bd9a66926..9b5a3fa36 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -29,8 +29,8 @@ import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.schoolYearStart import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toTimestamp import java.time.LocalDate @@ -225,7 +225,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag } override fun showDatePickerDialog(currentDate: LocalDate) { - val baseDate = currentDate.schoolYearStart + val baseDate = currentDate.firstSchoolDayInSchoolYear val rangeStart = baseDate.toTimestamp() val rangeEnd = LocalDate.now().plusWeeks(1).toTimestamp() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt index 12168f142..0f285b13d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt @@ -11,6 +11,8 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogHomeworkAddBinding import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.SchoolDaysValidator +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toTimestamp @@ -98,14 +100,17 @@ class HomeworkAddDialog : BaseDialogFragment(), Homewo } override fun showDatePickerDialog(currentDate: LocalDate) { + val rangeStart = LocalDate.now().toTimestamp() + val rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear.toTimestamp() val constraintsBuilder = CalendarConstraints.Builder().apply { - setStart(LocalDate.now().toEpochDay()) + setStart(rangeStart) + setEnd(rangeEnd) + setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) } - val datePicker = - MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() + val datePicker = MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setSelection(currentDate.toTimestamp()) + .build() datePicker.addOnPositiveButtonClickListener { date = it.toLocalDateTime().toLocalDate() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt index 49d094b78..3bbed18b7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt @@ -16,7 +16,7 @@ import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.schoolYearStart +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toTimestamp import java.time.LocalDate @@ -112,7 +112,7 @@ class LuckyNumberHistoryFragment : } override fun showDatePickerDialog(currentDate: LocalDate) { - val baseDate = currentDate.schoolYearStart + val baseDate = currentDate.firstSchoolDayInSchoolYear val rangeStart = baseDate.toTimestamp() val rangeEnd = LocalDate.now().plusWeeks(1).toTimestamp() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index b49d11c12..dd1136398 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -24,9 +24,9 @@ import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragme import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.schoolYearEnd -import io.github.wulkanowy.utils.schoolYearStart +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toTimestamp import java.time.LocalDate @@ -194,9 +194,9 @@ class TimetableFragment : BaseFragment(R.layout.fragme } override fun showDatePickerDialog(currentDate: LocalDate) { - val baseDate = currentDate.schoolYearStart + val baseDate = currentDate.firstSchoolDayInSchoolYear val rangeStart = baseDate.toTimestamp() - val rangeEnd = LocalDate.now().schoolYearEnd.toTimestamp() + val rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear.toTimestamp() val constraintsBuilder = CalendarConstraints.Builder().apply { setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt index fdc8b8874..c2ce80289 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.timetable.additional import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.databinding.ItemTimetableAdditionalBinding @@ -14,6 +15,8 @@ class AdditionalLessonsAdapter @Inject constructor() : var items = emptyList() + var onDeleteClickListener: (timetableAdditional: TimetableAdditional) -> Unit = {} + override fun getItemCount() = items.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( @@ -25,8 +28,12 @@ class AdditionalLessonsAdapter @Inject constructor() : val item = items[position] with(holder.binding) { - additionalLessonItemTime.text = "${item.start.toFormattedString("HH:mm")} - ${item.end.toFormattedString("HH:mm")}" + additionalLessonItemTime.text = + "${item.start.toFormattedString("HH:mm")} - ${item.end.toFormattedString("HH:mm")}" additionalLessonItemSubject.text = item.subject + + additionalLessonItemDelete.isVisible = item.isAddedByUser + additionalLessonItemDelete.setOnClickListener { onDeleteClickListener(item) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt index e8fb9e440..b2a4a9a32 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.timetable.additional import android.os.Bundle import android.view.View +import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.datepicker.CalendarConstraints import com.google.android.material.datepicker.MaterialDatePicker @@ -10,13 +11,15 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.databinding.FragmentTimetableAdditionalBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.timetable.additional.add.AdditionalLessonAddDialog import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.schoolYearEnd -import io.github.wulkanowy.utils.schoolYearStart +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toTimestamp import java.time.LocalDate @@ -53,7 +56,9 @@ class AdditionalLessonsFragment : override fun initView() { with(binding.additionalLessonsRecycler) { layoutManager = LinearLayoutManager(context) - adapter = additionalLessonsAdapter + adapter = additionalLessonsAdapter.apply { + onDeleteClickListener = { presenter.onDeleteLessonsSelected(it) } + } addItemDecoration(DividerItemDecoration(context)) } @@ -61,9 +66,7 @@ class AdditionalLessonsFragment : additionalLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) additionalLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) additionalLessonsSwipe.setProgressBackgroundColorSchemeColor( - requireContext().getThemeAttrColor( - R.attr.colorSwipeRefresh - ) + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) ) additionalLessonsErrorRetry.setOnClickListener { presenter.onRetry() } additionalLessonsErrorDetails.setOnClickListener { presenter.onDetailsClick() } @@ -72,6 +75,8 @@ class AdditionalLessonsFragment : additionalLessonsNavDate.setOnClickListener { presenter.onPickDate() } additionalLessonsNextButton.setOnClickListener { presenter.onNextDay() } + openAddAdditionalLessonButton.setOnClickListener { presenter.onAdditionalLessonAddButtonClicked() } + additionalLessonsNavContainer.elevation = requireContext().dpToPx(8f) } } @@ -90,6 +95,10 @@ class AdditionalLessonsFragment : } } + override fun showSuccessMessage() { + getString(R.string.additional_lessons_delete_success) + } + override fun updateNavigationDay(date: String) { binding.additionalLessonsNavDate.text = date } @@ -131,21 +140,24 @@ class AdditionalLessonsFragment : binding.additionalLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE } + override fun showAddAdditionalLessonDialog() { + (activity as? MainActivity)?.showDialogFragment(AdditionalLessonAddDialog.newInstance()) + } + override fun showDatePickerDialog(currentDate: LocalDate) { val now = LocalDate.now() - val startOfSchoolYear = now.schoolYearStart.toTimestamp() - val endOfSchoolYear = now.schoolYearEnd.toTimestamp() + val startOfSchoolYear = now.firstSchoolDayInSchoolYear.toTimestamp() + val endOfSchoolYear = now.lastSchoolDayInSchoolYear.toTimestamp() val constraintsBuilder = CalendarConstraints.Builder().apply { setValidator(SchoolDaysValidator(startOfSchoolYear, endOfSchoolYear)) setStart(startOfSchoolYear) setEnd(endOfSchoolYear) } - val datePicker = - MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() + val datePicker = MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setSelection(currentDate.toTimestamp()) + .build() datePicker.addOnPositiveButtonClickListener { val date = it.toLocalDateTime() @@ -157,6 +169,18 @@ class AdditionalLessonsFragment : } } + override fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional) { + AlertDialog.Builder(requireContext()) + .setTitle(getString(R.string.additional_lessons_delete_title)) + .setItems( + arrayOf( + getString(R.string.additional_lessons_delete_one), + getString(R.string.additional_lessons_delete_series) + ) + ) { _, position -> presenter.onDeleteDialogSelectItem(position, timetableAdditional) } + .show() + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt index 3496e141a..742a8d592 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.timetable.additional import android.annotation.SuppressLint import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository @@ -20,6 +21,7 @@ import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import timber.log.Timber import java.time.LocalDate import javax.inject.Inject @@ -63,6 +65,10 @@ class AdditionalLessonsPresenter @Inject constructor( view?.showDatePickerDialog(currentDate) } + fun onAdditionalLessonAddButtonClicked() { + view?.showAddAdditionalLessonDialog() + } + fun onDateSet(year: Int, month: Int, day: Int) { loadData(LocalDate.of(year, month, day)) reloadView() @@ -98,6 +104,36 @@ class AdditionalLessonsPresenter @Inject constructor( }.launch("holidays") } + fun onDeleteLessonsSelected(timetableAdditional: TimetableAdditional) { + if (timetableAdditional.repeatId == null) { + deleteAdditionalLessons(timetableAdditional, false) + } else { + view?.showDeleteLessonDialog(timetableAdditional) + } + } + + fun onDeleteDialogSelectItem(position: Int, timetableAdditional: TimetableAdditional) { + deleteAdditionalLessons(timetableAdditional, position == 1) + } + + private fun deleteAdditionalLessons( + timetableAdditional: TimetableAdditional, + deleteSeries: Boolean + ) { + presenterScope.launch { + Timber.i("Additional Lesson delete start") + runCatching { timetableRepository.deleteAdditional(timetableAdditional, deleteSeries) } + .onSuccess { + Timber.i("Additional Lesson delete: Success") + view?.showSuccessMessage() + } + .onFailure { + Timber.i("Additional Lesson delete result: An exception occurred") + errorHandler.dispatch(it) + } + } + } + private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { currentDate = date @@ -111,7 +147,7 @@ class AdditionalLessonsPresenter @Inject constructor( Status.SUCCESS -> { Timber.i("Loading additional lessons lessons result: Success") view?.apply { - updateData(it.data!!.additional.sortedBy { item -> item.date }) + updateData(it.data!!.additional.sortedBy { item -> item.start }) showEmpty(it.data.additional.isEmpty()) showErrorView(false) showContent(it.data.additional.isNotEmpty()) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt index 97eb2ae7b..03466d69d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt @@ -35,4 +35,10 @@ interface AdditionalLessonsView : BaseView { fun showNextButton(show: Boolean) fun showDatePickerDialog(currentDate: LocalDate) + + fun showAddAdditionalLessonDialog() + + fun showSuccessMessage() + + fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt new file mode 100644 index 000000000..f57841c9c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt @@ -0,0 +1,188 @@ +package io.github.wulkanowy.ui.modules.timetable.additional.add + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.widget.doOnTextChanged +import com.google.android.material.datepicker.CalendarConstraints +import com.google.android.material.datepicker.MaterialDatePicker +import com.google.android.material.timepicker.MaterialTimePicker +import com.google.android.material.timepicker.TimeFormat +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.DialogAdditionalAddBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.SchoolDaysValidator +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.toLocalDateTime +import io.github.wulkanowy.utils.toTimestamp +import java.time.LocalDate +import java.time.LocalTime +import javax.inject.Inject + +@AndroidEntryPoint +class AdditionalLessonAddDialog : BaseDialogFragment(), + AdditionalLessonAddView { + + @Inject + lateinit var presenter: AdditionalLessonAddPresenter + + companion object { + fun newInstance() = AdditionalLessonAddDialog() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, 0) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = DialogAdditionalAddBinding.inflate(inflater).apply { binding = this }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView(this) + } + + override fun initView() { + with(binding) { + additionalLessonDialogStartEdit.doOnTextChanged { _, _, _, _ -> + additionalLessonDialogStart.isErrorEnabled = false + additionalLessonDialogStart.error = null + } + additionalLessonDialogEndEdit.doOnTextChanged { _, _, _, _ -> + additionalLessonDialogEnd.isErrorEnabled = false + additionalLessonDialogEnd.error = null + } + additionalLessonDialogDateEdit.doOnTextChanged { _, _, _, _ -> + additionalLessonDialogDate.isErrorEnabled = false + additionalLessonDialogDate.error = null + } + additionalLessonDialogContentEdit.doOnTextChanged { _, _, _, _ -> + additionalLessonDialogContent.isErrorEnabled = false + additionalLessonDialogContent.error = null + } + + additionalLessonDialogAdd.setOnClickListener { + presenter.onAddAdditionalClicked( + start = additionalLessonDialogStartEdit.text?.toString(), + end = additionalLessonDialogEndEdit.text?.toString(), + date = additionalLessonDialogDateEdit.text?.toString(), + content = additionalLessonDialogContentEdit.text?.toString(), + isRepeat = additionalLessonDialogRepeat.isChecked + ) + } + additionalLessonDialogClose.setOnClickListener { dismiss() } + additionalLessonDialogDateEdit.setOnClickListener { presenter.showDatePicker() } + additionalLessonDialogStartEdit.setOnClickListener { presenter.showStartTimePicker() } + additionalLessonDialogEndEdit.setOnClickListener { presenter.showEndTimePicker() } + } + } + + override fun showSuccessMessage() { + showMessage(getString(R.string.additional_lessons_add_success)) + } + + override fun setErrorDateRequired() { + with(binding.additionalLessonDialogDate) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorStartRequired() { + with(binding.additionalLessonDialogStart) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorEndRequired() { + with(binding.additionalLessonDialogEnd) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorContentRequired() { + with(binding.additionalLessonDialogContent) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + override fun setErrorIncorrectEndTime() { + with(binding.additionalLessonDialogEnd) { + isErrorEnabled = true + error = getString(R.string.additional_lessons_end_time_error) + } + } + + override fun closeDialog() { + dismiss() + } + + override fun showDatePickerDialog(selectedDate: LocalDate) { + val rangeStart = LocalDate.now().toTimestamp() + val rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear.toTimestamp() + val constraintsBuilder = CalendarConstraints.Builder().apply { + setStart(rangeStart) + setEnd(rangeEnd) + setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) + } + val datePicker = MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setSelection(selectedDate.toTimestamp()) + .build() + + datePicker.addOnPositiveButtonClickListener { + val date = it.toLocalDateTime().toLocalDate() + presenter.onDateSelected(date) + binding.additionalLessonDialogDateEdit.setText(date.toFormattedString()) + } + + if (!parentFragmentManager.isStateSaved) { + datePicker.show(parentFragmentManager, null) + } + } + + override fun showStartTimePickerDialog(selectedTime: LocalTime) { + showTimePickerDialog(selectedTime) { + presenter.onStartTimeSelected(it) + binding.additionalLessonDialogStartEdit.setText(it.toString()) + } + } + + override fun showEndTimePickerDialog(selectedTime: LocalTime) { + showTimePickerDialog(selectedTime) { + presenter.onEndTimeSelected(it) + binding.additionalLessonDialogEndEdit.setText(it.toString()) + } + } + + private fun showTimePickerDialog(defaultTime: LocalTime, onTimeSelected: (LocalTime) -> Unit) { + val timePicker = MaterialTimePicker.Builder() + .setTimeFormat(TimeFormat.CLOCK_24H) + .setHour(defaultTime.hour) + .setMinute(defaultTime.minute) + .build() + + timePicker.addOnPositiveButtonClickListener { + onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute)) + } + + if (!parentFragmentManager.isStateSaved) { + timePicker.show(parentFragmentManager, null) + } + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt new file mode 100644 index 000000000..7bed5619e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt @@ -0,0 +1,166 @@ +package io.github.wulkanowy.ui.modules.timetable.additional.add + +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.toLocalDate +import kotlinx.coroutines.launch +import timber.log.Timber +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.temporal.ChronoUnit +import java.util.UUID +import javax.inject.Inject + +class AdditionalLessonAddPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val timetableRepository: TimetableRepository, + private val semesterRepository: SemesterRepository +) : BasePresenter(errorHandler, studentRepository) { + + private var selectedStartTime = LocalTime.of(15, 0) + + private var selectedEndTime = LocalTime.of(15, 45) + + private var selectedDate = LocalDate.now() + + override fun onAttachView(view: AdditionalLessonAddView) { + super.onAttachView(view) + view.initView() + Timber.i("AdditionalLesson details view was initialized") + } + + fun showDatePicker() { + view?.showDatePickerDialog(selectedDate) + } + + fun showStartTimePicker() { + view?.showStartTimePickerDialog(selectedStartTime) + } + + fun showEndTimePicker() { + view?.showEndTimePickerDialog(selectedEndTime) + } + + fun onStartTimeSelected(time: LocalTime) { + selectedStartTime = time + } + + fun onEndTimeSelected(time: LocalTime) { + selectedEndTime = time + } + + fun onDateSelected(date: LocalDate) { + selectedDate = date + } + + fun onAddAdditionalClicked( + start: String?, + end: String?, + date: String?, + content: String?, + isRepeat: Boolean + ) { + if (isUserInputValid(start, end, date, content)) { + addAdditionalLesson( + start = LocalTime.parse(start!!), + end = LocalTime.parse(end), + date = date!!.toLocalDate(), + subject = content!!, + isRepeat = isRepeat + ) + } + } + + private fun isUserInputValid( + start: String?, + end: String?, + date: String?, + content: String? + ): Boolean { + var isValid = true + + if (start.isNullOrBlank()) { + view?.setErrorStartRequired() + isValid = false + } + + if (end.isNullOrBlank()) { + view?.setErrorEndRequired() + isValid = false + } + + if (date.isNullOrBlank()) { + view?.setErrorDateRequired() + isValid = false + } + + if (content.isNullOrBlank()) { + view?.setErrorContentRequired() + isValid = false + } + + if (selectedStartTime >= selectedEndTime) { + view?.setErrorIncorrectEndTime() + isValid = false + } + + return isValid + } + + private fun addAdditionalLesson( + start: LocalTime, + end: LocalTime, + date: LocalDate, + subject: String, + isRepeat: Boolean + ) { + presenterScope.launch { + val semester = runCatching { + val student = studentRepository.getCurrentStudent() + semesterRepository.getCurrentSemester(student) + } + .onFailure(errorHandler::dispatch) + .getOrNull() ?: return@launch + + val weeks = if (isRepeat) { + ChronoUnit.WEEKS.between(date, date.lastSchoolDayInSchoolYear) + } else 0 + val uniqueRepeatId = UUID.randomUUID().takeIf { isRepeat } + + val lessonsToAdd = (0..weeks).map { + TimetableAdditional( + studentId = semester.studentId, + diaryId = semester.diaryId, + start = LocalDateTime.of(date, start), + end = LocalDateTime.of(date, end), + date = date.plusWeeks(it), + subject = subject + ).apply { + isAddedByUser = true + repeatId = uniqueRepeatId + } + } + + Timber.i("AdditionalLesson insert start") + runCatching { timetableRepository.saveAdditionalList(lessonsToAdd) } + .onSuccess { + Timber.i("AdditionalLesson insert: Success") + view?.run { + showSuccessMessage() + closeDialog() + } + } + .onFailure { + Timber.i("AdditionalLesson insert result: An exception occurred") + errorHandler.dispatch(it) + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt new file mode 100644 index 000000000..0df53815b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt @@ -0,0 +1,30 @@ +package io.github.wulkanowy.ui.modules.timetable.additional.add + +import io.github.wulkanowy.ui.base.BaseView +import java.time.LocalDate +import java.time.LocalTime + +interface AdditionalLessonAddView : BaseView { + + fun initView() + + fun closeDialog() + + fun showDatePickerDialog(selectedDate: LocalDate) + + fun showStartTimePickerDialog(selectedTime: LocalTime) + + fun showEndTimePickerDialog(selectedTime: LocalTime) + + fun showSuccessMessage() + + fun setErrorDateRequired() + + fun setErrorStartRequired() + + fun setErrorEndRequired() + + fun setErrorContentRequired() + + fun setErrorIncorrectEndTime() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt index a6b126447..4d5e3e1a5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt @@ -18,10 +18,10 @@ import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear import io.github.wulkanowy.utils.getCompatDrawable import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.schoolYearEnd -import io.github.wulkanowy.utils.schoolYearStart +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toTimestamp import java.time.LocalDate @@ -68,9 +68,7 @@ class CompletedLessonsFragment : completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) completedLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) completedLessonsSwipe.setProgressBackgroundColorSchemeColor( - requireContext().getThemeAttrColor( - R.attr.colorSwipeRefresh - ) + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) ) completedLessonErrorRetry.setOnClickListener { presenter.onRetry() } completedLessonErrorDetails.setOnClickListener { presenter.onDetailsClick() } @@ -154,19 +152,18 @@ class CompletedLessonsFragment : override fun showDatePickerDialog(currentDate: LocalDate) { val now = LocalDate.now() - val startOfSchoolYear = now.schoolYearStart.toTimestamp() - val endOfSchoolYear = now.schoolYearEnd.toTimestamp() + val startOfSchoolYear = now.firstSchoolDayInSchoolYear.toTimestamp() + val endOfSchoolYear = now.lastSchoolDayInSchoolYear.toTimestamp() val constraintsBuilder = CalendarConstraints.Builder().apply { setValidator(SchoolDaysValidator(startOfSchoolYear, endOfSchoolYear)) setStart(startOfSchoolYear) setEnd(endOfSchoolYear) } - val datePicker = - MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() + val datePicker = MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setSelection(currentDate.toTimestamp()) + .build() datePicker.addOnPositiveButtonClickListener { val date = it.toLocalDateTime() diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt index 94b6a2191..bebb6c541 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -85,35 +85,31 @@ inline val LocalDate.previousOrSameSchoolDay: LocalDate inline val LocalDate.weekDayName: String get() = format(DateTimeFormatter.ofPattern("EEEE", Locale.getDefault())) -inline val LocalDate.monday: LocalDate - get() = with(MONDAY) +inline val LocalDate.monday: LocalDate get() = with(MONDAY) -inline val LocalDate.sunday: LocalDate - get() = with(SUNDAY) +inline val LocalDate.sunday: LocalDate get() = with(SUNDAY) /** * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335) */ -inline val LocalDate.isHolidays: Boolean - get() = isBefore(firstSchoolDay) && isAfter(lastSchoolDay) +val LocalDate.isHolidays: Boolean + get() = isBefore(firstSchoolDayInCalendarYear) && isAfter(lastSchoolDayInCalendarYear) -inline val LocalDate.firstSchoolDay: LocalDate - get() = LocalDate.of(year, 9, 1).run { - when (dayOfWeek) { - FRIDAY, SATURDAY, SUNDAY -> with(firstInMonth(MONDAY)) - else -> this - } +val LocalDate.firstSchoolDayInSchoolYear: LocalDate + get() = withYear(if (this.monthValue <= 6) this.year - 1 else this.year).firstSchoolDayInCalendarYear + +val LocalDate.lastSchoolDayInSchoolYear: LocalDate + get() = withYear(if (this.monthValue > 6) this.year + 1 else this.year).lastSchoolDayInCalendarYear + +fun LocalDate.getLastSchoolDayIfHoliday(schoolYear: Int): LocalDate { + val date = LocalDate.of(schoolYear.getSchoolYearByMonth(monthValue), monthValue, dayOfMonth) + + if (date.isHolidays) { + return date.lastSchoolDayInCalendarYear } -inline val LocalDate.lastSchoolDay: LocalDate - get() = LocalDate.of(year, 6, 20) - .with(next(FRIDAY)) - -inline val LocalDate.schoolYearStart: LocalDate - get() = withYear(if (this.monthValue <= 6) this.year - 1 else this.year).firstSchoolDay - -inline val LocalDate.schoolYearEnd: LocalDate - get() = withYear(if (this.monthValue > 6) this.year + 1 else this.year).lastSchoolDay + return date +} private fun Int.getSchoolYearByMonth(monthValue: Int): Int { return when (monthValue) { @@ -122,12 +118,15 @@ private fun Int.getSchoolYearByMonth(monthValue: Int): Int { } } -fun LocalDate.getLastSchoolDayIfHoliday(schoolYear: Int): LocalDate { - val date = LocalDate.of(schoolYear.getSchoolYearByMonth(monthValue), monthValue, dayOfMonth) - - if (date.isHolidays) { - return date.lastSchoolDay +private inline val LocalDate.firstSchoolDayInCalendarYear: LocalDate + get() = LocalDate.of(year, 9, 1).run { + when (dayOfWeek) { + FRIDAY, SATURDAY, SUNDAY -> with(firstInMonth(MONDAY)) + else -> this + } } - return date -} +private inline val LocalDate.lastSchoolDayInCalendarYear: LocalDate + get() = LocalDate.of(year, 6, 20) + .with(next(FRIDAY)) + diff --git a/app/src/main/res/drawable/ic_all_clock.xml b/app/src/main/res/drawable/ic_all_clock.xml new file mode 100644 index 000000000..4b98ed233 --- /dev/null +++ b/app/src/main/res/drawable/ic_all_clock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_calendat_all.xml b/app/src/main/res/drawable/ic_calendat_all.xml new file mode 100644 index 000000000..5908035ed --- /dev/null +++ b/app/src/main/res/drawable/ic_calendat_all.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/dialog_additional_add.xml b/app/src/main/res/layout/dialog_additional_add.xml new file mode 100644 index 000000000..884018e55 --- /dev/null +++ b/app/src/main/res/layout/dialog_additional_add.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_grade.xml b/app/src/main/res/layout/dialog_grade.xml index 9ed2bc068..9c52c1d0b 100644 --- a/app/src/main/res/layout/dialog_grade.xml +++ b/app/src/main/res/layout/dialog_grade.xml @@ -204,7 +204,6 @@ android:insetBottom="0dp" android:minWidth="88dp" android:text="@string/all_close" /> - diff --git a/app/src/main/res/layout/dialog_homework_add.xml b/app/src/main/res/layout/dialog_homework_add.xml index b9b8d1a2e..74c583aa1 100644 --- a/app/src/main/res/layout/dialog_homework_add.xml +++ b/app/src/main/res/layout/dialog_homework_add.xml @@ -5,8 +5,8 @@ android:layout_height="match_parent" android:fillViewport="true" android:minWidth="300dp" - android:paddingStart="24dp" - android:paddingEnd="24dp"> + android:paddingStart="8dp" + android:paddingEnd="8dp"> + app:startIconDrawable="@drawable/ic_calendat_all"> @@ -66,7 +66,7 @@ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="0dp" + android:layout_marginHorizontal="16dp" android:layout_marginTop="16dp" android:hint="@string/all_teacher"> @@ -81,7 +81,7 @@ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="0dp" + android:layout_marginHorizontal="16dp" android:layout_marginTop="16dp" android:layout_marginBottom="16dp" android:hint="@string/all_content"> @@ -105,13 +105,12 @@ android:layout_gravity="center_vertical" android:layout_marginStart="0dp" android:layout_marginLeft="0dp" - android:layout_marginTop="8dp" - android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:insetLeft="0dp" android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minWidth="88dp" android:text="@string/all_close" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> @@ -126,6 +125,7 @@ android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minWidth="88dp" android:text="@string/all_add" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/fragment_timetable_additional.xml b/app/src/main/res/layout/fragment_timetable_additional.xml index a71f7545b..ec25f9a07 100644 --- a/app/src/main/res/layout/fragment_timetable_additional.xml +++ b/app/src/main/res/layout/fragment_timetable_additional.xml @@ -26,6 +26,8 @@ android:id="@+id/additionalLessonsRecycler" android:layout_width="match_parent" android:layout_height="match_parent" + android:clipToPadding="false" + android:paddingBottom="64dp" tools:listitem="@layout/item_timetable_additional" /> @@ -108,6 +110,18 @@ android:text="@string/all_retry" /> + + + tools:maxLines="2" + tools:text="@tools:sample/lorem/random" /> + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 97c8f0345..dff5babfd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -198,6 +198,17 @@ Additional lessons Show additional lessons No info about additional lessons + New lesson + New additional lesson + Additional lesson added successfully + Additional lesson deleted successfully + Repeat weekly + Delete additional lesson + Just this lesson + All in the series + Start time + End time + End time must be greater than start time diff --git a/build.gradle b/build.gradle index bd74bab41..9de28f819 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.6.0' + kotlin_version = '1.6.10' about_libraries = '8.9.4' hilt_version = "2.40.5" } From e26860ea5a87cc0fc19f1694d7d9252d33d8b5e3 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Thu, 23 Dec 2021 13:58:26 +0100 Subject: [PATCH 04/30] Fix state restoring in GradeStatistics (#1667) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../statistics/GradeStatisticsFragment.kt | 11 ++++-- .../statistics/GradeStatisticsPresenter.kt | 39 ++++++++++++------- .../grade/statistics/GradeStatisticsView.kt | 2 +- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt index 35d007749..2af59c011 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt @@ -33,6 +33,7 @@ class GradeStatisticsFragment : companion object { private const val SAVED_CHART_TYPE = "CURRENT_TYPE" + private const val SAVED_SUBJECT_NAME = "SUBJECT_NAME" fun newInstance() = GradeStatisticsFragment() } @@ -46,8 +47,9 @@ class GradeStatisticsFragment : binding = FragmentGradeStatisticsBinding.bind(view) messageContainer = binding.gradeStatisticsRecycler presenter.onAttachView( - this, - savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType + view = this, + type = savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType, + subjectName = savedInstanceState?.getSerializable(SAVED_SUBJECT_NAME) as? String, ) } @@ -56,6 +58,7 @@ class GradeStatisticsFragment : with(binding.gradeStatisticsRecycler) { layoutManager = LinearLayoutManager(requireContext()) + statisticsAdapter.currentDataType = presenter.currentType adapter = statisticsAdapter } @@ -81,7 +84,8 @@ class GradeStatisticsFragment : } } - override fun updateSubjects(data: ArrayList) { + override fun updateSubjects(data: List, selectedIndex: Int) { + binding.gradeStatisticsSubjects.setSelection(selectedIndex) with(subjectsAdapter) { clear() addAll(data) @@ -161,6 +165,7 @@ class GradeStatisticsFragment : override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putSerializable(SAVED_CHART_TYPE, presenter.currentType) + outState.putSerializable(SAVED_SUBJECT_NAME, presenter.currentSubjectName) } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index 53eccad65..e536f473a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -31,16 +31,22 @@ class GradeStatisticsPresenter @Inject constructor( private var currentSemesterId = 0 - private var currentSubjectName: String = "Wszystkie" + var currentSubjectName: String = "Wszystkie" + private set private lateinit var lastError: Throwable var currentType: GradeStatisticsItem.DataType = GradeStatisticsItem.DataType.PARTIAL private set - fun onAttachView(view: GradeStatisticsView, type: GradeStatisticsItem.DataType?) { + fun onAttachView( + view: GradeStatisticsView, + type: GradeStatisticsItem.DataType?, + subjectName: String? + ) { super.onAttachView(view) currentType = type ?: GradeStatisticsItem.DataType.PARTIAL + currentSubjectName = subjectName ?: currentSubjectName view.initView() errorHandler.showErrorMessage = ::showErrorViewOnError } @@ -127,12 +133,17 @@ class GradeStatisticsPresenter @Inject constructor( when (it.status) { Status.LOADING -> Timber.i("Loading grade stats subjects started") Status.SUCCESS -> { - subjects = it.data!! - + subjects = requireNotNull(it.data) Timber.i("Loading grade stats subjects result: Success") + view?.run { - view?.updateSubjects(ArrayList(it.data.map { subject -> subject.name })) showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) + updateSubjects( + data = it.data.map { subject -> subject.name }, + selectedIndex = it.data.indexOfFirst { subject -> + subject.name == currentSubjectName + }, + ) } } Status.ERROR -> { @@ -151,9 +162,11 @@ class GradeStatisticsPresenter @Inject constructor( ) { Timber.i("Loading grade stats data started") - currentSubjectName = - if (preferencesRepository.showAllSubjectsOnStatisticsList) "Wszystkie" else subjectName currentType = type + currentSubjectName = when { + preferencesRepository.showAllSubjectsOnStatisticsList -> "Wszystkie" + else -> subjectName + } flowWithResourceIn { val student = studentRepository.getCurrentStudent() @@ -200,9 +213,9 @@ class GradeStatisticsPresenter @Inject constructor( showRefresh(true) showProgress(false) updateData( - if (isNoContent) emptyList() else it.data!!, - preferencesRepository.gradeColorTheme, - preferencesRepository.showAllSubjectsOnStatisticsList + newItems = if (isNoContent) emptyList() else it.data!!, + newTheme = preferencesRepository.gradeColorTheme, + showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList, ) showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) } @@ -215,9 +228,9 @@ class GradeStatisticsPresenter @Inject constructor( showEmpty(isNoContent) showErrorView(false) updateData( - if (isNoContent) emptyList() else it.data, - preferencesRepository.gradeColorTheme, - preferencesRepository.showAllSubjectsOnStatisticsList + newItems = if (isNoContent) emptyList() else it.data, + newTheme = preferencesRepository.gradeColorTheme, + showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList, ) showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt index 8e9a206bc..4333bb0a9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt @@ -12,7 +12,7 @@ interface GradeStatisticsView : BaseView { fun initView() - fun updateSubjects(data: ArrayList) + fun updateSubjects(data: List, selectedIndex: Int) fun updateData( newItems: List, From 497083be97dafd11821498f054fe8686dadcf72e Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Sat, 25 Dec 2021 06:46:24 +0100 Subject: [PATCH 05/30] Update timetable to next day if there is no more lessons today (#1551) --- .idea/codeStyles/Project.xml | 15 ----- .../timetablewidget/TimetableWidgetFactory.kt | 12 ++++ .../TimetableWidgetProvider.kt | 56 +++++++++++-------- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index ab7844747..1f93faefd 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -2,14 +2,6 @@ \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index f3d760f09..51b790e88 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -22,12 +22,14 @@ import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getCurrentThemeWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.toFirstResult import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.runBlocking import timber.log.Timber import java.time.LocalDate +import java.time.ZoneOffset class TimetableWidgetFactory( private val timetableRepository: TimetableRepository, @@ -70,6 +72,16 @@ class TimetableWidgetFactory( updateTheme(appWidgetId) lessons = getLessons(date, studentId) + + if (date == LocalDate.now()) { + val todayLastLessonEndTimestamp = + lessons.maxOf { it.end }.toEpochSecond(ZoneOffset.UTC) + sharedPref.putLong( + getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), + todayLastLessonEndTimestamp, + true + ) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 0f0691160..641d22612 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -3,10 +3,7 @@ package io.github.wulkanowy.ui.modules.timetablewidget import android.annotation.SuppressLint import android.app.PendingIntent import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_DELETED -import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS +import android.appwidget.AppWidgetManager.* import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK @@ -25,22 +22,14 @@ import io.github.wulkanowy.services.HiltBroadcastReceiver import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.splash.SplashActivity -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.PendingIntentCompat -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.createNameInitialsDrawable -import io.github.wulkanowy.utils.getCompatColor -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.nickOrName -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber import java.time.LocalDate -import java.time.LocalDate.now +import java.time.LocalDateTime +import java.time.ZoneOffset import javax.inject.Inject @AndroidEntryPoint @@ -76,6 +65,9 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { fun getDateWidgetKey(appWidgetId: Int) = "timetable_widget_date_$appWidgetId" + fun getTodayLastLessonEndDateTimeWidgetKey(appWidgetId: Int) = + "timetable_widget_today_last_lesson_end_date_time_$appWidgetId" + fun getStudentWidgetKey(appWidgetId: Int) = "timetable_widget_student_$appWidgetId" fun getThemeWidgetKey(appWidgetId: Int) = "timetable_widget_theme_$appWidgetId" @@ -100,7 +92,8 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId -> val student = getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) - updateWidget(context, appWidgetId, now().nextOrSameSchoolDay, student) + + updateWidget(context, appWidgetId, getWidgetDateToLoad(appWidgetId), student) } } else { val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE) @@ -112,15 +105,17 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { val savedDate = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0)) val date = when (buttonType) { - BUTTON_RESET -> now().nextOrSameSchoolDay + BUTTON_RESET -> getWidgetDateToLoad(toggledWidgetId) BUTTON_NEXT -> savedDate.nextSchoolDay BUTTON_PREV -> savedDate.previousSchoolDay - else -> now().nextOrSameSchoolDay + else -> getWidgetDateToLoad(toggledWidgetId) + } + if (!buttonType.isNullOrBlank()) { + analytics.logEvent( + "changed_timetable_widget_day", + "button" to buttonType + ) } - if (!buttonType.isNullOrBlank()) analytics.logEvent( - "changed_timetable_widget_day", - "button" to buttonType - ) updateWidget(context, toggledWidgetId, date, student) } } @@ -278,4 +273,21 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { avatarDrawable.draw(canvas) return avatarBitmap } + + private fun getWidgetDateToLoad(appWidgetId: Int): LocalDate { + val lastLessonEndTimestamp = + sharedPref.getLong(getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), 0) + val lastLessonEndDateTime = + LocalDateTime.ofEpochSecond(lastLessonEndTimestamp, 0, ZoneOffset.UTC) + + val todayDate = LocalDate.now() + val isLastLessonEndDateNow = lastLessonEndDateTime.toLocalDate() == todayDate + val isLastLessonEndDateAfterNowTime = LocalDateTime.now() > lastLessonEndDateTime + + return if (isLastLessonEndDateNow && isLastLessonEndDateAfterNowTime) { + todayDate.nextSchoolDay + } else { + todayDate.nextOrSameSchoolDay + } + } } From 65f114ce05233582ab2d1c57d264c7f32239f35b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Dec 2021 05:54:17 +0000 Subject: [PATCH 06/30] Bump kotlinx-serialization-json from 1.3.1 to 1.3.2 (#1733) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1ff8b9268..efbe536cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -178,7 +178,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "androidx.core:core-ktx:1.7.0" From cd12c4c89145b7fad1ac5fd775ce47dbf94a8d83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Dec 2021 05:54:34 +0000 Subject: [PATCH 07/30] Bump agconnect-crash from 1.6.2.300 to 1.6.3.200 (#1732) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index efbe536cc..77c47fe0b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -238,7 +238,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:20.5.0' hmsImplementation 'com.huawei.hms:hianalytics:6.3.2.300' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.2.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.3.200' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From f718147ae9b6a81edc79eaca255f027a0ab2f770 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Dec 2021 05:55:01 +0000 Subject: [PATCH 08/30] Bump agcp from 1.6.2.300 to 1.6.3.200 (#1730) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9de28f819..b06af2e16 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.0.4' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.10' - classpath 'com.huawei.agconnect:agcp:1.6.2.300' + classpath 'com.huawei.agconnect:agcp:1.6.3.200' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.0" From 8560fd7e81f1230558feca872ca9a783ef99c692 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Dec 2021 08:38:27 +0000 Subject: [PATCH 09/30] Bump coroutines from 1.5.2 to 1.6.0 (#1731) --- app/build.gradle | 2 +- .../io/github/wulkanowy/utils/FlowUtilsKtTest.kt | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 77c47fe0b..135863562 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -170,7 +170,7 @@ ext { room = "2.4.0" chucker = "3.5.2" mockk = "1.12.1" - coroutines = "1.5.2" + coroutines = "1.6.0" } dependencies { diff --git a/app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt b/app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt index 375a24038..57045a29d 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt @@ -1,23 +1,21 @@ package io.github.wulkanowy.utils -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerifyOrder -import io.mockk.just -import io.mockk.mockk +import io.mockk.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceTimeBy import org.junit.Test import kotlin.test.assertEquals @OptIn(ExperimentalCoroutinesApi::class) class FlowUtilsKtTest { - private val testScope = TestCoroutineScope() + private val testScope = TestScope(UnconfinedTestDispatcher()) @Test fun `fetch from two places with same remote data`() { From 2eee50ad815ed920a19c10b1e5c24baeff8811b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 27 Dec 2021 07:58:57 +0100 Subject: [PATCH 10/30] Replace view pager in login activity with simple fragment transactions (#1686) --- .../ui/modules/login/LoginActivity.kt | 134 ++++++------------ .../wulkanowy/ui/modules/login/LoginData.kt | 9 ++ .../ui/modules/login/LoginErrorHandler.kt | 5 +- .../ui/modules/login/LoginPresenter.kt | 55 +------ .../wulkanowy/ui/modules/login/LoginView.kt | 11 -- .../login/advanced/LoginAdvancedFragment.kt | 17 +-- .../login/advanced/LoginAdvancedPresenter.kt | 26 +++- .../login/advanced/LoginAdvancedView.kt | 5 +- .../modules/login/form/LoginFormFragment.kt | 20 +-- .../modules/login/form/LoginFormPresenter.kt | 6 +- .../ui/modules/login/form/LoginFormView.kt | 5 +- .../login/recover/LoginRecoverFragment.kt | 4 +- .../login/recover/RecoverErrorHandler.kt | 5 +- .../LoginStudentSelectFragment.kt | 27 ++-- .../LoginStudentSelectPresenter.kt | 19 +-- .../login/symbol/LoginSymbolFragment.kt | 24 ++-- .../login/symbol/LoginSymbolPresenter.kt | 54 +++---- .../modules/login/symbol/LoginSymbolView.kt | 2 +- .../settings/advanced/AdvancedFragment.kt | 4 - app/src/main/res/layout/activity_login.xml | 11 +- .../res/layout/fragment_login_recover.xml | 2 +- .../ui/modules/login/LoginPresenterTest.kt | 57 -------- .../LoginStudentSelectPresenterTest.kt | 2 +- 23 files changed, 186 insertions(+), 318 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/ui/modules/login/LoginPresenterTest.kt diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt index e607cef15..d7d77f73d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt @@ -4,18 +4,19 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ActivityLoginBinding import io.github.wulkanowy.ui.base.BaseActivity -import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment import io.github.wulkanowy.utils.UpdateHelper -import io.github.wulkanowy.utils.setOnSelectPageListener import javax.inject.Inject @AndroidEntryPoint @@ -24,21 +25,10 @@ class LoginActivity : BaseActivity(), Logi @Inject override lateinit var presenter: LoginPresenter - private val pagerAdapter by lazy { - BaseFragmentPagerAdapter( - fragmentManager = supportFragmentManager, - pagesCount = 5, - lifecycle = lifecycle, - ) - } - @Inject lateinit var updateHelper: UpdateHelper - override val currentViewIndex get() = binding.loginViewpager.currentItem - companion object { - fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) } @@ -51,6 +41,50 @@ class LoginActivity : BaseActivity(), Logi presenter.onAttachView(this) updateHelper.checkAndInstallUpdates(this) + + if (savedInstanceState == null) { + openFragment(LoginFormFragment.newInstance(), clearBackStack = true) + } + } + + override fun initView() { + with(requireNotNull(supportActionBar)) { + setDisplayHomeAsUpEnabled(true) + setDisplayShowTitleEnabled(false) + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) onBackPressed() + return true + } + + fun showActionBar(show: Boolean) { + supportActionBar?.run { if (show) show() else hide() } + } + + fun navigateToSymbolFragment(loginData: LoginData) { + openFragment(LoginSymbolFragment.newInstance(loginData)) + } + + fun navigateToStudentSelect(studentsWithSemesters: List) { + openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters)) + } + + fun onAdvancedLoginClick() { + openFragment(LoginAdvancedFragment.newInstance()) + } + + fun onRecoverClick() { + openFragment(LoginRecoverFragment.newInstance()) + } + + private fun openFragment(fragment: Fragment, clearBackStack: Boolean = false) { + supportFragmentManager.commit { + replace(R.id.loginContainer, fragment) + setReorderingAllowed(true) + if (!clearBackStack) addToBackStack(fragment::class.java.name) + } } override fun onResume() { @@ -64,78 +98,4 @@ class LoginActivity : BaseActivity(), Logi super.onActivityResult(requestCode, resultCode, data) updateHelper.onActivityResult(requestCode, resultCode) } - - override fun initView() { - with(requireNotNull(supportActionBar)) { - setDisplayHomeAsUpEnabled(true) - setDisplayShowTitleEnabled(false) - } - - with(binding.loginViewpager) { - adapter = pagerAdapter - isUserInputEnabled = false - offscreenPageLimit = 2 - setOnSelectPageListener(presenter::onViewSelected) - } - - with(pagerAdapter) { - containerId = binding.loginViewpager.id - itemFactory = { - when (it) { - 0 -> LoginFormFragment.newInstance() - 1 -> LoginSymbolFragment.newInstance() - 2 -> LoginStudentSelectFragment.newInstance() - 3 -> LoginAdvancedFragment.newInstance() - 4 -> LoginRecoverFragment.newInstance() - else -> throw IllegalStateException() - } - } - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) onBackPressed() - return true - } - - override fun switchView(index: Int) { - binding.loginViewpager.setCurrentItem(index, false) - } - - override fun showActionBar(show: Boolean) { - supportActionBar?.run { if (show) show() else hide() } - } - - override fun onBackPressed() { - presenter.onBackPressed { super.onBackPressed() } - } - - override fun notifyInitSymbolFragment(loginData: Triple) { - (pagerAdapter.getFragmentInstance(1) as? LoginSymbolFragment) - ?.onParentInitSymbolFragment(loginData) - } - - override fun notifyInitStudentSelectFragment(studentsWithSemesters: List) { - (pagerAdapter.getFragmentInstance(2) as? LoginStudentSelectFragment) - ?.onParentInitStudentSelectFragment(studentsWithSemesters) - } - - fun onFormFragmentAccountLogged( - studentsWithSemesters: List, - loginData: Triple - ) { - presenter.onFormViewAccountLogged(studentsWithSemesters, loginData) - } - - fun onSymbolFragmentAccountLogged(studentsWithSemesters: List) { - presenter.onSymbolViewAccountLogged(studentsWithSemesters) - } - - fun onAdvancedLoginClick() { - presenter.onAdvancedLoginClick() - } - - fun onRecoverClick() { - presenter.onRecoverClick() - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt new file mode 100644 index 000000000..5d4743589 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.ui.modules.login + +import java.io.Serializable + +data class LoginData( + val login: String, + val password: String, + val baseUrl: String, +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt index ea7215cea..37ab71dce 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt @@ -12,8 +12,9 @@ import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.ui.base.ErrorHandler import javax.inject.Inject -class LoginErrorHandler @Inject constructor(@ApplicationContext context: Context) : - ErrorHandler(context) { +class LoginErrorHandler @Inject constructor( + @ApplicationContext context: Context, +) : ErrorHandler(context) { var onBadCredentials: (String?) -> Unit = {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt index aa1e7eced..9031cb8ab 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.login -import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler @@ -14,59 +13,7 @@ class LoginPresenter @Inject constructor( override fun onAttachView(view: LoginView) { super.onAttachView(view) - with(view) { - initView() - showActionBar(false) - } + view.initView() Timber.i("Login view was initialized") } - - fun onFormViewAccountLogged(studentsWithSemesters: List, loginData: Triple) { - view?.apply { - if (studentsWithSemesters.isEmpty()) { - Timber.i("Switch to symbol form") - notifyInitSymbolFragment(loginData) - switchView(1) - } else { - Timber.i("Switch to student select") - notifyInitStudentSelectFragment(studentsWithSemesters) - switchView(2) - } - } - } - - fun onSymbolViewAccountLogged(studentsWithSemesters: List) { - view?.apply { - Timber.i("Switch to student select") - notifyInitStudentSelectFragment(studentsWithSemesters) - switchView(2) - } - } - - fun onAdvancedLoginClick() { - view?.switchView(3) - } - - fun onRecoverClick() { - view?.switchView(4) - } - - fun onViewSelected(index: Int) { - view?.apply { - when (index) { - 0 -> showActionBar(false) - 1, 2, 3, 4 -> showActionBar(true) - } - } - } - - fun onBackPressed(default: () -> Unit) { - Timber.i("Back pressed in login view") - view?.apply { - when (currentViewIndex) { - 1, 2, 3, 4 -> switchView(0) - else -> default() - } - } - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt index 2a5cf316a..a0949e6d9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt @@ -1,19 +1,8 @@ package io.github.wulkanowy.ui.modules.login -import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView interface LoginView : BaseView { - val currentViewIndex: Int - fun initView() - - fun switchView(index: Int) - - fun showActionBar(show: Boolean) - - fun notifyInitSymbolFragment(loginData: Triple) - - fun notifyInitStudentSelectFragment(studentsWithSemesters: List) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt index bc29cd146..37dcb38b3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt @@ -13,6 +13,7 @@ import io.github.wulkanowy.databinding.FragmentLoginAdvancedBinding import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.form.LoginSymbolAdapter import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.setOnEditorDoneSignIn @@ -80,6 +81,8 @@ class LoginAdvancedFragment : } override fun initView() { + (requireActivity() as LoginActivity).showActionBar(true) + hostKeys = resources.getStringArray(R.array.hosts_keys) hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) @@ -320,14 +323,12 @@ class LoginAdvancedFragment : binding.loginFormContainer.visibility = if (show) VISIBLE else GONE } - override fun notifyParentAccountLogged(studentsWithSemesters: List) { - (activity as? LoginActivity)?.onFormFragmentAccountLogged( - studentsWithSemesters, Triple( - binding.loginFormUsername.text.toString(), - binding.loginFormPass.text.toString(), - resources.getStringArray(R.array.hosts_values)[1] - ) - ) + override fun navigateToSymbol(loginData: LoginData) { + (activity as? LoginActivity)?.navigateToSymbolFragment(loginData) + } + + override fun navigateToStudentSelect(studentsWithSemesters: List) { + (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters) } override fun onResume() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt index 17d8c5ecb..3543a3041 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading @@ -77,7 +78,9 @@ class LoginAdvancedPresenter @Inject constructor( clearPassError() clearUsernameError() if (formHostValue.contains("fakelog")) { - setDefaultCredentials("jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999") + setDefaultCredentials( + "jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999" + ) } setSymbol(formHostSymbol) updateUsernameLabel() @@ -136,12 +139,21 @@ class LoginAdvancedPresenter @Inject constructor( } Status.SUCCESS -> { Timber.i("Login result: Success") - analytics.logEvent("registration_form", + analytics.logEvent( + "registration_form", "success" to true, "students" to it.data!!.size, "error" to "No error" ) - view?.notifyParentAccountLogged(it.data) + val loginData = LoginData( + login = view?.formUsernameValue.orEmpty().trim(), + password = view?.formPassValue.orEmpty().trim(), + baseUrl = view?.formHostValue.orEmpty().trim() + ) + when (it.data.size) { + 0 -> view?.navigateToSymbol(loginData) + else -> view?.navigateToStudentSelect(it.data) + } } Status.ERROR -> { Timber.i("Login result: An exception occurred") @@ -172,8 +184,12 @@ class LoginAdvancedPresenter @Inject constructor( return when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) { Sdk.Mode.API -> studentRepository.getStudentsApi(pin, symbol, token) - Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper(email, password, endpoint, symbol) - Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid(email, password, endpoint, symbol) + Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper( + email, password, endpoint, symbol + ) + Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid( + email, password, endpoint, symbol + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt index 1d2b2856d..f9b84f1ab 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.login.advanced import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.login.LoginData interface LoginAdvancedView : BaseView { @@ -69,7 +70,9 @@ interface LoginAdvancedView : BaseView { fun showContent(show: Boolean) - fun notifyParentAccountLogged(studentsWithSemesters: List) + fun navigateToSymbol(loginData: LoginData) + + fun navigateToStudentSelect(studentsWithSemesters: List) fun setErrorPinRequired() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt index c741da429..d31f5cf0f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt @@ -13,6 +13,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.FragmentLoginFormBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.openEmailClient @@ -68,6 +69,8 @@ class LoginFormFragment : BaseFragment(R.layout.fragme } override fun initView() { + (requireActivity() as LoginActivity).showActionBar(false) + hostKeys = resources.getStringArray(R.array.hosts_keys) hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) @@ -203,11 +206,9 @@ class LoginFormFragment : BaseFragment(R.layout.fragme binding.loginFormVersion.text = "v${appInfo.versionName}" } - override fun notifyParentAccountLogged( - studentsWithSemesters: List, - loginData: Triple - ) { - (activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, loginData) + override fun showContact(show: Boolean) { + binding.loginFormContact.visibility = if (show) VISIBLE else GONE + binding.loginFormRecoverLink.visibility = if (show) GONE else VISIBLE } override fun openPrivacyPolicyPage() { @@ -217,9 +218,12 @@ class LoginFormFragment : BaseFragment(R.layout.fragme ) } - override fun showContact(show: Boolean) { - binding.loginFormContact.visibility = if (show) VISIBLE else GONE - binding.loginFormRecoverLink.visibility = if (show) GONE else VISIBLE + override fun navigateToSymbol(loginData: LoginData) { + (activity as? LoginActivity)?.navigateToSymbolFragment(loginData) + } + + override fun navigateToStudentSelect(studentsWithSemesters: List) { + (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters) } override fun openAdvancedLogin() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index 1002549cf..49be6fbb6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -4,6 +4,7 @@ import androidx.core.net.toUri import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading @@ -118,7 +119,10 @@ class LoginFormPresenter @Inject constructor( "scrapperBaseUrl" to host, "error" to "No error" ) - view?.notifyParentAccountLogged(it.data, Triple(email, password, host)) + when (it.data.size) { + 0 -> view?.navigateToSymbol(LoginData(email, password, host)) + else -> view?.navigateToStudentSelect(it.data) + } } Status.ERROR -> { Timber.i("Login result: An exception occurred") diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt index 300573559..8003975db 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.login.form import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.login.LoginData interface LoginFormView : BaseView { @@ -57,7 +58,9 @@ interface LoginFormView : BaseView { fun showVersion() - fun notifyParentAccountLogged(studentsWithSemesters: List, loginData: Triple) + fun navigateToSymbol(loginData: LoginData) + + fun navigateToStudentSelect(studentsWithSemesters: List) fun openPrivacyPolicyPage() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index a91dfb618..fe32a14f1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -69,6 +69,8 @@ class LoginRecoverFragment : } override fun initView() { + (requireActivity() as LoginActivity).showActionBar(true) + hostKeys = resources.getStringArray(R.array.hosts_keys) hostValues = resources.getStringArray(R.array.hosts_values) hostSymbols = resources.getStringArray(R.array.hosts_symbols) @@ -80,7 +82,7 @@ class LoginRecoverFragment : loginRecoverButton.setOnClickListener { presenter.onRecoverClick() } loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() } loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() } - loginRecoverLogin.setOnClickListener { (activity as LoginActivity).switchView(0) } + loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressed() } } with(bindingLocal.loginRecoverHost) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt index ac4c03130..28686d626 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt @@ -8,8 +8,9 @@ import io.github.wulkanowy.sdk.scrapper.exception.NoAccountFoundException import io.github.wulkanowy.ui.base.ErrorHandler import javax.inject.Inject -class RecoverErrorHandler @Inject constructor(@ApplicationContext context: Context) : - ErrorHandler(context) { +class RecoverErrorHandler @Inject constructor( + @ApplicationContext context: Context, +) : ErrorHandler(context) { var onInvalidUsername: (String) -> Unit = {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index 87cb505c4..6c910fe03 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -4,17 +4,18 @@ import android.os.Bundle import android.view.View import android.view.View.GONE import android.view.View.VISIBLE +import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser -import java.io.Serializable import javax.inject.Inject @AndroidEntryPoint @@ -32,18 +33,27 @@ class LoginStudentSelectFragment : lateinit var appInfo: AppInfo companion object { - const val SAVED_STUDENTS = "STUDENTS" + const val ARG_STUDENTS = "STUDENTS" - fun newInstance() = LoginStudentSelectFragment() + fun newInstance(studentsWithSemesters: List) = + LoginStudentSelectFragment().apply { + arguments = bundleOf(ARG_STUDENTS to studentsWithSemesters) + } } + @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentLoginStudentSelectBinding.bind(view) - presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_STUDENTS)) + presenter.onAttachView( + view = this, + students = requireArguments().getSerializable(ARG_STUDENTS) as List, + ) } override fun initView() { + (requireActivity() as LoginActivity).showActionBar(true) + loginAdapter.onClickListener = presenter::onItemSelected with(binding) { @@ -82,15 +92,6 @@ class LoginStudentSelectFragment : binding.loginStudentSelectSignIn.isEnabled = enable } - fun onParentInitStudentSelectFragment(studentsWithSemesters: List) { - presenter.onParentInitStudentSelectView(studentsWithSemesters) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putSerializable(SAVED_STUDENTS, presenter.students as Serializable) - } - override fun showContact(show: Boolean) { binding.loginStudentSelectContact.visibility = if (show) VISIBLE else GONE } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index f0f5586cc..8c475a672 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -11,7 +11,6 @@ import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber -import java.io.Serializable import javax.inject.Inject class LoginStudentSelectPresenter @Inject constructor( @@ -22,11 +21,9 @@ class LoginStudentSelectPresenter @Inject constructor( private var lastError: Throwable? = null - var students = emptyList() - private val selectedStudents = mutableListOf() - fun onAttachView(view: LoginStudentSelectView, students: Serializable?) { + fun onAttachView(view: LoginStudentSelectView, students: List) { super.onAttachView(view) with(view) { initView() @@ -38,20 +35,14 @@ class LoginStudentSelectPresenter @Inject constructor( } } - if (students is List<*> && students.isNotEmpty()) { - loadData(students.filterIsInstance()) - } + if (students.size == 1) registerStudents(students) + loadData(students) } fun onSignIn() { registerStudents(selectedStudents) } - fun onParentInitStudentSelectView(studentsWithSemesters: List) { - loadData(studentsWithSemesters) - if (studentsWithSemesters.size == 1) registerStudents(studentsWithSemesters) - } - fun onItemSelected(studentWithSemester: StudentWithSemesters, alreadySaved: Boolean) { if (alreadySaved) return @@ -72,7 +63,6 @@ class LoginStudentSelectPresenter @Inject constructor( private fun loadData(studentsWithSemesters: List) { resetSelectedState() - this.students = studentsWithSemesters flowWithResource { studentRepository.getSavedStudents(false) }.onEach { when (it.status) { @@ -143,7 +133,8 @@ class LoginStudentSelectPresenter @Inject constructor( "success" to (error != null), "scrapperBaseUrl" to student.student.scrapperBaseUrl, "symbol" to student.student.symbol, - "error" to (error?.message?.ifBlank { "No message" } ?: "No error")) + "error" to (error?.message?.ifBlank { "No message" } ?: "No error") + ) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index a8086935b..58bdf6cef 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -7,6 +7,7 @@ import android.view.View.VISIBLE import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_NULL import android.widget.ArrayAdapter +import androidx.core.os.bundleOf import androidx.core.text.parseAsHtml import androidx.core.widget.doOnTextChanged import dagger.hilt.android.AndroidEntryPoint @@ -15,6 +16,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.openEmailClient @@ -35,7 +37,9 @@ class LoginSymbolFragment : companion object { private const val SAVED_LOGIN_DATA = "LOGIN_DATA" - fun newInstance() = LoginSymbolFragment() + fun newInstance(loginData: LoginData) = LoginSymbolFragment().apply { + arguments = bundleOf(SAVED_LOGIN_DATA to loginData) + } } override val symbolNameError: CharSequence? @@ -44,10 +48,15 @@ class LoginSymbolFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentLoginSymbolBinding.bind(view) - presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_LOGIN_DATA)) + presenter.onAttachView( + view = this, + loginData = requireArguments().getSerializable(SAVED_LOGIN_DATA) as LoginData, + ) } override fun initView() { + (requireActivity() as LoginActivity).showActionBar(true) + with(binding) { loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) } loginSymbolFaq.setOnClickListener { presenter.onFaqClick() } @@ -70,12 +79,9 @@ class LoginSymbolFragment : } } - fun onParentInitSymbolFragment(loginData: Triple) { - presenter.onParentInitSymbolView(loginData) - } - override fun setLoginToHeading(login: String) { - binding.loginSymbolHeader.text = getString(R.string.login_header_symbol, login).parseAsHtml() + binding.loginSymbolHeader.text = + getString(R.string.login_header_symbol, login).parseAsHtml() } override fun setErrorSymbolIncorrect() { @@ -119,8 +125,8 @@ class LoginSymbolFragment : binding.loginSymbolContainer.visibility = if (show) VISIBLE else GONE } - override fun notifyParentAccountLogged(studentsWithSemesters: List) { - (activity as? LoginActivity)?.onSymbolFragmentAccountLogged(studentsWithSemesters) + override fun navigateToStudentSelect(studentsWithSemesters: List) { + (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt index 1ba7e5b32..7e195893f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.login.symbol import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading @@ -10,7 +11,6 @@ import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber -import java.io.Serializable import javax.inject.Inject class LoginSymbolPresenter @Inject constructor( @@ -21,25 +21,15 @@ class LoginSymbolPresenter @Inject constructor( private var lastError: Throwable? = null - var loginData: Triple? = null + lateinit var loginData: LoginData - @Suppress("UNCHECKED_CAST") - fun onAttachView(view: LoginSymbolView, savedLoginData: Serializable?) { + fun onAttachView(view: LoginSymbolView, loginData: LoginData) { super.onAttachView(view) - view.run { + this.loginData = loginData + with(view) { initView() showContact(false) - } - if (savedLoginData is Triple<*, *, *>) { - loginData = savedLoginData as Triple - view.setLoginToHeading(requireNotNull(loginData?.first)) - } - } - - fun onParentInitSymbolView(loginData: Triple) { - this.loginData = loginData - view?.apply { - setLoginToHeading(loginData.first) + setLoginToHeading(loginData.login) clearAndFocusSymbol() showSoftKeyboard() } @@ -50,11 +40,6 @@ class LoginSymbolPresenter @Inject constructor( } fun attemptLogin(symbol: String) { - if (loginData == null) { - Timber.w("LoginSymbolPresenter - Login data is null") - return - } - if (symbol.isBlank()) { view?.setErrorSymbolRequire() return @@ -62,9 +47,9 @@ class LoginSymbolPresenter @Inject constructor( flowWithResource { studentRepository.getStudentsScrapper( - email = loginData!!.first, - password = loginData!!.second, - scrapperBaseUrl = loginData!!.third, + email = loginData.login, + password = loginData.password, + scrapperBaseUrl = loginData.baseUrl, symbol = symbol, ) }.onEach { @@ -76,21 +61,24 @@ class LoginSymbolPresenter @Inject constructor( showContent(false) } Status.SUCCESS -> { - view?.run { - if (it.data!!.isEmpty()) { + when (it.data?.size) { + 0 -> { Timber.i("Login with symbol result: Empty student list") - setErrorSymbolIncorrect() - view?.showContact(true) - } else { + view?.run { + setErrorSymbolIncorrect() + showContact(true) + } + } + else -> { Timber.i("Login with symbol result: Success") - notifyParentAccountLogged(it.data) + view?.navigateToStudentSelect(requireNotNull(it.data)) } } analytics.logEvent( "registration_symbol", "success" to true, "students" to it.data!!.size, - "scrapperBaseUrl" to loginData?.third, + "scrapperBaseUrl" to loginData.baseUrl, "symbol" to symbol, "error" to "No error" ) @@ -101,7 +89,7 @@ class LoginSymbolPresenter @Inject constructor( "registration_symbol", "success" to false, "students" to -1, - "scrapperBaseUrl" to loginData?.third, + "scrapperBaseUrl" to loginData.baseUrl, "symbol" to symbol, "error" to it.error!!.message.ifNullOrBlank { "No message" } ) @@ -123,6 +111,6 @@ class LoginSymbolPresenter @Inject constructor( } fun onEmailClick() { - view?.openEmail(loginData?.third.orEmpty(), lastError?.message.ifNullOrBlank { "empty" }) + view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank { "empty" }) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt index 75523a7c7..527895b77 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt @@ -27,7 +27,7 @@ interface LoginSymbolView : BaseView { fun showContent(show: Boolean) - fun notifyParentAccountLogged(studentsWithSemesters: List) + fun navigateToStudentSelect(studentsWithSemesters: List) fun showContact(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt index a2265b044..bef726cad 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt @@ -4,7 +4,6 @@ import android.content.SharedPreferences import android.os.Bundle import android.view.View import androidx.preference.PreferenceFragmentCompat -import com.yariksoffice.lingver.Lingver import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity @@ -24,9 +23,6 @@ class AdvancedFragment : PreferenceFragmentCompat(), @Inject lateinit var appInfo: AppInfo - @Inject - lateinit var lingver: Lingver - override val titleStringId get() = R.string.pref_settings_advanced_title override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 1d5b5280d..912792638 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,5 +1,5 @@ @@ -10,8 +10,11 @@ android:layout_height="wrap_content" android:background="@android:color/transparent" /> - + android:layout_height="0dp" + android:layout_weight="1" + tools:layout="@layout/fragment_login_form" /> + diff --git a/app/src/main/res/layout/fragment_login_recover.xml b/app/src/main/res/layout/fragment_login_recover.xml index 76bdad228..028d6bc1f 100644 --- a/app/src/main/res/layout/fragment_login_recover.xml +++ b/app/src/main/res/layout/fragment_login_recover.xml @@ -155,7 +155,7 @@ android:orientation="vertical" android:visibility="invisible" tools:ignore="UseCompoundDrawables" - tools:visibility="visible"> + tools:visibility="gone"> Date: Mon, 27 Dec 2021 08:10:30 +0100 Subject: [PATCH 11/30] Fix that an incorrect day would be selected in MaterialDatePicker (#1723) --- .../modules/attendance/AttendanceFragment.kt | 38 ++++---------- .../ui/modules/attendance/AttendanceView.kt | 2 +- .../modules/homework/add/HomeworkAddDialog.kt | 38 +++++--------- .../modules/homework/add/HomeworkAddView.kt | 2 +- .../history/LuckyNumberHistoryFragment.kt | 38 ++++---------- .../history/LuckyNumberHistoryView.kt | 2 +- .../ui/modules/timetable/TimetableFragment.kt | 38 ++++---------- .../ui/modules/timetable/TimetableView.kt | 2 +- .../additional/AdditionalLessonsFragment.kt | 36 ++++--------- .../additional/AdditionalLessonsView.kt | 2 +- .../add/AdditionalLessonAddDialog.kt | 36 ++++--------- .../completed/CompletedLessonsFragment.kt | 36 ++++--------- .../completed/CompletedLessonsView.kt | 2 +- .../utils/MaterialDatePickerUtils.kt | 51 +++++++++++++++++++ .../wulkanowy/utils/SchooldaysValidator.kt | 16 ------ .../github/wulkanowy/utils/TimeExtension.kt | 11 ++-- .../wulkanowy/utils/TimeExtensionTest.kt | 20 +++++++- 17 files changed, 153 insertions(+), 217 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/SchooldaysValidator.kt diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index 9b5a3fa36..84af1ca32 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -14,8 +14,6 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.MaterialDatePicker import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance @@ -27,12 +25,10 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.toLocalDateTime -import io.github.wulkanowy.utils.toTimestamp +import io.github.wulkanowy.utils.openMaterialDatePicker import java.time.LocalDate import javax.inject.Inject @@ -224,29 +220,15 @@ class AttendanceFragment : BaseFragment(R.layout.frag (activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson)) } - override fun showDatePickerDialog(currentDate: LocalDate) { - val baseDate = currentDate.firstSchoolDayInSchoolYear - val rangeStart = baseDate.toTimestamp() - val rangeEnd = LocalDate.now().plusWeeks(1).toTimestamp() - - val constraintsBuilder = CalendarConstraints.Builder().apply { - setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) - setStart(rangeStart) - setEnd(rangeEnd) - } - val datePicker = MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() - - datePicker.addOnPositiveButtonClickListener { - val date = it.toLocalDateTime() - presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth) - } - - if (!parentFragmentManager.isStateSaved) { - datePicker.show(parentFragmentManager, null) - } + override fun showDatePickerDialog(selectedDate: LocalDate) { + openMaterialDatePicker( + selected = selectedDate, + rangeStart = selectedDate.firstSchoolDayInSchoolYear, + rangeEnd = LocalDate.now().plusWeeks(1), + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) } override fun showExcuseDialog() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt index 7ddd75f48..b0123065a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt @@ -48,7 +48,7 @@ interface AttendanceView : BaseView { fun showAttendanceDialog(lesson: Attendance) - fun showDatePickerDialog(currentDate: LocalDate) + fun showDatePickerDialog(selectedDate: LocalDate) fun showExcuseDialog() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt index 0f285b13d..c2aff2b13 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt @@ -5,17 +5,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.widget.doOnTextChanged -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.MaterialDatePicker import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogHomeworkAddBinding import io.github.wulkanowy.ui.base.BaseDialogFragment -import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.openMaterialDatePicker import io.github.wulkanowy.utils.toFormattedString -import io.github.wulkanowy.utils.toLocalDateTime -import io.github.wulkanowy.utils.toTimestamp import java.time.LocalDate import javax.inject.Inject @@ -25,6 +21,7 @@ class HomeworkAddDialog : BaseDialogFragment(), Homewo @Inject lateinit var presenter: HomeworkAddPresenter + // todo: move it to presenter private var date: LocalDate? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -99,27 +96,16 @@ class HomeworkAddDialog : BaseDialogFragment(), Homewo dismiss() } - override fun showDatePickerDialog(currentDate: LocalDate) { - val rangeStart = LocalDate.now().toTimestamp() - val rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear.toTimestamp() - val constraintsBuilder = CalendarConstraints.Builder().apply { - setStart(rangeStart) - setEnd(rangeEnd) - setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) - } - val datePicker = MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() - - datePicker.addOnPositiveButtonClickListener { - date = it.toLocalDateTime().toLocalDate() - binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString()) - } - - if (!parentFragmentManager.isStateSaved) { - datePicker.show(this.parentFragmentManager, null) - } + override fun showDatePickerDialog(selectedDate: LocalDate) { + openMaterialDatePicker( + selected = selectedDate, + rangeStart = LocalDate.now(), + rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear, + onDateSelected = { + date = it + binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString()) + } + ) } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt index 3bb304d9c..91414ae2f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt @@ -17,5 +17,5 @@ interface HomeworkAddView : BaseView { fun closeDialog() - fun showDatePickerDialog(currentDate: LocalDate) + fun showDatePickerDialog(selectedDate: LocalDate) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt index 3bbed18b7..53f06cacd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt @@ -5,8 +5,6 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.MaterialDatePicker import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.LuckyNumber @@ -14,11 +12,9 @@ import io.github.wulkanowy.databinding.FragmentLuckyNumberHistoryBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear -import io.github.wulkanowy.utils.toLocalDateTime -import io.github.wulkanowy.utils.toTimestamp +import io.github.wulkanowy.utils.openMaterialDatePicker import java.time.LocalDate import javax.inject.Inject @@ -111,29 +107,15 @@ class LuckyNumberHistoryFragment : binding.luckyNumberHistoryNextButton.visibility = if (show) VISIBLE else View.INVISIBLE } - override fun showDatePickerDialog(currentDate: LocalDate) { - val baseDate = currentDate.firstSchoolDayInSchoolYear - val rangeStart = baseDate.toTimestamp() - val rangeEnd = LocalDate.now().plusWeeks(1).toTimestamp() - - val constraintsBuilder = CalendarConstraints.Builder().apply { - setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) - setStart(rangeStart) - setEnd(rangeEnd) - } - val datePicker = MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() - - datePicker.addOnPositiveButtonClickListener { - val date = it.toLocalDateTime() - presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth) - } - - if (!parentFragmentManager.isStateSaved) { - datePicker.show(parentFragmentManager, null) - } + override fun showDatePickerDialog(selectedDate: LocalDate) { + openMaterialDatePicker( + selected = selectedDate, + rangeStart = selectedDate.firstSchoolDayInSchoolYear, + rangeEnd = LocalDate.now().plusWeeks(1), + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) } override fun showContent(show: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt index 331e4ff86..7b9b0294f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt @@ -28,7 +28,7 @@ interface LuckyNumberHistoryView : BaseView { fun showNextButton(show: Boolean) - fun showDatePickerDialog(currentDate: LocalDate) + fun showDatePickerDialog(selectedDate: LocalDate) fun showContent(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index dd1136398..f59c6432c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -9,8 +9,6 @@ import android.view.View.GONE import android.view.View.VISIBLE import androidx.core.text.parseAsHtml import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.MaterialDatePicker import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable @@ -22,13 +20,11 @@ import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.timetable.additional.AdditionalLessonsFragment import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear -import io.github.wulkanowy.utils.toLocalDateTime -import io.github.wulkanowy.utils.toTimestamp +import io.github.wulkanowy.utils.openMaterialDatePicker import java.time.LocalDate import javax.inject.Inject @@ -193,29 +189,15 @@ class TimetableFragment : BaseFragment(R.layout.fragme (activity as? MainActivity)?.showDialogFragment(TimetableDialog.newInstance(lesson)) } - override fun showDatePickerDialog(currentDate: LocalDate) { - val baseDate = currentDate.firstSchoolDayInSchoolYear - val rangeStart = baseDate.toTimestamp() - val rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear.toTimestamp() - - val constraintsBuilder = CalendarConstraints.Builder().apply { - setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) - setStart(rangeStart) - setEnd(rangeEnd) - } - val datePicker = MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() - - datePicker.addOnPositiveButtonClickListener { - val date = it.toLocalDateTime() - presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth) - } - - if (!parentFragmentManager.isStateSaved) { - datePicker.show(parentFragmentManager, null) - } + override fun showDatePickerDialog(selectedDate: LocalDate) { + openMaterialDatePicker( + selected = selectedDate, + rangeStart = selectedDate.firstSchoolDayInSchoolYear, + rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear, + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) } override fun openAdditionalLessonsView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt index eaf75cb0e..4f6af4b9e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt @@ -48,7 +48,7 @@ interface TimetableView : BaseView { fun showTimetableDialog(lesson: Timetable) - fun showDatePickerDialog(currentDate: LocalDate) + fun showDatePickerDialog(selectedDate: LocalDate) fun popView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt index b2a4a9a32..043fa1f7d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt @@ -4,8 +4,6 @@ import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.MaterialDatePicker import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.TimetableAdditional @@ -15,13 +13,11 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.timetable.additional.add.AdditionalLessonAddDialog import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear -import io.github.wulkanowy.utils.toLocalDateTime -import io.github.wulkanowy.utils.toTimestamp +import io.github.wulkanowy.utils.openMaterialDatePicker import java.time.LocalDate import javax.inject.Inject @@ -144,29 +140,17 @@ class AdditionalLessonsFragment : (activity as? MainActivity)?.showDialogFragment(AdditionalLessonAddDialog.newInstance()) } - override fun showDatePickerDialog(currentDate: LocalDate) { + override fun showDatePickerDialog(selectedDate: LocalDate) { val now = LocalDate.now() - val startOfSchoolYear = now.firstSchoolDayInSchoolYear.toTimestamp() - val endOfSchoolYear = now.lastSchoolDayInSchoolYear.toTimestamp() - val constraintsBuilder = CalendarConstraints.Builder().apply { - setValidator(SchoolDaysValidator(startOfSchoolYear, endOfSchoolYear)) - setStart(startOfSchoolYear) - setEnd(endOfSchoolYear) - } - val datePicker = MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() - - datePicker.addOnPositiveButtonClickListener { - val date = it.toLocalDateTime() - presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth) - } - - if (!parentFragmentManager.isStateSaved) { - datePicker.show(parentFragmentManager, null) - } + openMaterialDatePicker( + selected = selectedDate, + rangeStart = now.firstSchoolDayInSchoolYear, + rangeEnd = now.lastSchoolDayInSchoolYear, + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) } override fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt index 03466d69d..76d37b754 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt @@ -34,7 +34,7 @@ interface AdditionalLessonsView : BaseView { fun showNextButton(show: Boolean) - fun showDatePickerDialog(currentDate: LocalDate) + fun showDatePickerDialog(selectedDate: LocalDate) fun showAddAdditionalLessonDialog() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt index f57841c9c..f82d64830 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt @@ -5,19 +5,15 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.widget.doOnTextChanged -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogAdditionalAddBinding import io.github.wulkanowy.ui.base.BaseDialogFragment -import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.openMaterialDatePicker import io.github.wulkanowy.utils.toFormattedString -import io.github.wulkanowy.utils.toLocalDateTime -import io.github.wulkanowy.utils.toTimestamp import java.time.LocalDate import java.time.LocalTime import javax.inject.Inject @@ -128,27 +124,15 @@ class AdditionalLessonAddDialog : BaseDialogFragment } override fun showDatePickerDialog(selectedDate: LocalDate) { - val rangeStart = LocalDate.now().toTimestamp() - val rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear.toTimestamp() - val constraintsBuilder = CalendarConstraints.Builder().apply { - setStart(rangeStart) - setEnd(rangeEnd) - setValidator(SchoolDaysValidator(rangeStart, rangeEnd)) - } - val datePicker = MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(selectedDate.toTimestamp()) - .build() - - datePicker.addOnPositiveButtonClickListener { - val date = it.toLocalDateTime().toLocalDate() - presenter.onDateSelected(date) - binding.additionalLessonDialogDateEdit.setText(date.toFormattedString()) - } - - if (!parentFragmentManager.isStateSaved) { - datePicker.show(parentFragmentManager, null) - } + openMaterialDatePicker( + selected = selectedDate, + rangeStart = LocalDate.now(), + rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear, + onDateSelected = { + presenter.onDateSelected(it) + binding.additionalLessonDialogDateEdit.setText(it.toFormattedString()) + } + ) } override fun showStartTimePickerDialog(selectedTime: LocalTime) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt index 4d5e3e1a5..34a69e6ab 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt @@ -6,8 +6,6 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.MaterialDatePicker import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.CompletedLesson @@ -16,14 +14,12 @@ import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.SchoolDaysValidator import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear import io.github.wulkanowy.utils.getCompatDrawable import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear -import io.github.wulkanowy.utils.toLocalDateTime -import io.github.wulkanowy.utils.toTimestamp +import io.github.wulkanowy.utils.openMaterialDatePicker import java.time.LocalDate import javax.inject.Inject @@ -150,29 +146,17 @@ class CompletedLessonsFragment : ) } - override fun showDatePickerDialog(currentDate: LocalDate) { + override fun showDatePickerDialog(selectedDate: LocalDate) { val now = LocalDate.now() - val startOfSchoolYear = now.firstSchoolDayInSchoolYear.toTimestamp() - val endOfSchoolYear = now.lastSchoolDayInSchoolYear.toTimestamp() - val constraintsBuilder = CalendarConstraints.Builder().apply { - setValidator(SchoolDaysValidator(startOfSchoolYear, endOfSchoolYear)) - setStart(startOfSchoolYear) - setEnd(endOfSchoolYear) - } - val datePicker = MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(currentDate.toTimestamp()) - .build() - - datePicker.addOnPositiveButtonClickListener { - val date = it.toLocalDateTime() - presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth) - } - - if (!parentFragmentManager.isStateSaved) { - datePicker.show(parentFragmentManager, null) - } + openMaterialDatePicker( + selected = selectedDate, + rangeStart = now.firstSchoolDayInSchoolYear, + rangeEnd = now.lastSchoolDayInSchoolYear, + onDateSelected = { + presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth) + } + ) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt index 7a98874e0..715ce01fc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsView.kt @@ -38,5 +38,5 @@ interface CompletedLessonsView : BaseView { fun showCompletedLessonDialog(completedLesson: CompletedLesson) - fun showDatePickerDialog(currentDate: LocalDate) + fun showDatePickerDialog(selectedDate: LocalDate) } diff --git a/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt new file mode 100644 index 000000000..14b5989b0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt @@ -0,0 +1,51 @@ +package io.github.wulkanowy.utils + +import androidx.fragment.app.Fragment +import com.google.android.material.datepicker.CalendarConstraints +import com.google.android.material.datepicker.MaterialDatePicker +import kotlinx.parcelize.Parcelize +import java.time.LocalDate +import java.time.ZoneOffset +import java.time.temporal.ChronoUnit + +fun Fragment.openMaterialDatePicker( + selected: LocalDate, + rangeStart: LocalDate, + rangeEnd: LocalDate, + onDateSelected: (LocalDate) -> Unit, +) { + val constraintsBuilder = CalendarConstraints.Builder().apply { + setValidator(CalendarDayRangeValidator(rangeStart, rangeEnd)) + setStart(rangeStart.toTimestamp(ZoneOffset.UTC)) + setEnd(rangeEnd.toTimestamp(ZoneOffset.UTC)) + } + + val datePicker = MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setSelection(selected.toTimestamp(ZoneOffset.UTC)) + .build() + + datePicker.addOnPositiveButtonClickListener { + val date = it.toLocalDateTime(ZoneOffset.UTC).toLocalDate() + onDateSelected(date) + } + + if (!parentFragmentManager.isStateSaved) { + datePicker.show(parentFragmentManager, null) + } +} + +@Parcelize +private class CalendarDayRangeValidator( + val start: LocalDate, + val end: LocalDate, +) : CalendarConstraints.DateValidator { + + override fun isValid(dateLong: Long): Boolean { + val date = dateLong.toLocalDateTime().toLocalDate() + val daysUntilEnd = date.until(end, ChronoUnit.DAYS) + val daysUntilStart = date.until(start, ChronoUnit.DAYS) + + return daysUntilStart <= 0 && daysUntilEnd >= 0 + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/SchooldaysValidator.kt b/app/src/main/java/io/github/wulkanowy/utils/SchooldaysValidator.kt deleted file mode 100644 index b6dd528f5..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/SchooldaysValidator.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.wulkanowy.utils - -import com.google.android.material.datepicker.CalendarConstraints -import kotlinx.parcelize.Parcelize -import java.time.temporal.ChronoUnit - -@Parcelize -class SchoolDaysValidator(val start: Long, val end: Long) : CalendarConstraints.DateValidator { - - override fun isValid(dateLong: Long): Boolean { - val date = dateLong.toLocalDateTime() - - return date.until(end.toLocalDateTime(), ChronoUnit.DAYS) >= 0 && - date.until(start.toLocalDateTime(), ChronoUnit.DAYS) <= 0 - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt index bebb6c541..355f3ab49 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -8,7 +8,6 @@ import java.time.DayOfWeek.SUNDAY import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime -import java.time.LocalTime import java.time.Month import java.time.ZoneId import java.time.ZoneOffset @@ -23,13 +22,13 @@ private const val DEFAULT_DATE_PATTERN = "dd.MM.yyyy" fun String.toLocalDate(format: String = DEFAULT_DATE_PATTERN): LocalDate = LocalDate.parse(this, DateTimeFormatter.ofPattern(format)) -fun LocalDateTime.toTimestamp() = - atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toInstant().toEpochMilli() +fun LocalDateTime.toTimestamp(tz: ZoneId = ZoneId.systemDefault()) = + atZone(tz).withZoneSameInstant(ZoneOffset.UTC).toInstant().toEpochMilli() -fun Long.toLocalDateTime(): LocalDateTime = - LocalDateTime.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault()) +fun Long.toLocalDateTime(tz: ZoneId = ZoneId.systemDefault()): LocalDateTime = + LocalDateTime.ofInstant(Instant.ofEpochMilli(this), tz) -fun LocalDate.toTimestamp() = atTime(LocalTime.now()).toTimestamp() +fun LocalDate.toTimestamp(tz: ZoneId = ZoneId.systemDefault()) = atStartOfDay().toTimestamp(tz) fun LocalDate.toFormattedString(pattern: String = DEFAULT_DATE_PATTERN): String = format(DateTimeFormatter.ofPattern(pattern)) diff --git a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt index 0ffbf78ec..9a3bf9fea 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt @@ -7,6 +7,7 @@ import org.junit.Test import java.time.LocalDate.of import java.time.LocalDateTime import java.time.Month.JANUARY +import java.time.ZoneOffset import java.util.Locale class TimeExtensionTest { @@ -25,7 +26,10 @@ class TimeExtensionTest { @Test fun toFormattedStringLocalDateTimeTest() { assertEquals("01.10.2018", LocalDateTime.of(2018, 10, 1, 10, 0, 0).toFormattedString()) - assertEquals("2018-10-01 10:00:00", LocalDateTime.of(2018, 10, 1, 10, 0, 0).toFormattedString("uuuu-MM-dd HH:mm:ss")) + assertEquals( + "2018-10-01 10:00:00", + LocalDateTime.of(2018, 10, 1, 10, 0, 0).toFormattedString("uuuu-MM-dd HH:mm:ss") + ) } @Test @@ -222,4 +226,18 @@ class TimeExtensionTest { assertEquals(of(2020, 10, 18), endExamsDay) } } + + @Test + fun getLocalDateToTimestampUTC() { + assertEquals(0L, of(1970, 1, 1).toTimestamp(ZoneOffset.UTC)) + assertEquals(946684800000L, of(2000, 1, 1).toTimestamp(ZoneOffset.UTC)) + assertEquals(1640131200000L, of(2021, 12, 22).toTimestamp(ZoneOffset.UTC)) + } + + @Test + fun getLocalDateTimeToUtcTimestamp() { + assertEquals(0L, LocalDateTime.of(1970, 1, 1, 0, 0, 0).toTimestamp(ZoneOffset.UTC)) + assertEquals(946684800000L, LocalDateTime.of(2000, 1, 1, 0, 0, 0).toTimestamp(ZoneOffset.UTC)) + assertEquals(1640131200000L, LocalDateTime.of(2021, 12, 22, 0, 0, 0).toTimestamp(ZoneOffset.UTC)) + } } From bd883c9f382ec61f16a3ad503deaa66cd7cf9cc5 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Mon, 27 Dec 2021 08:48:47 +0100 Subject: [PATCH 12/30] Add option to remove notifications captured from vulcan.hebe (#1716) --- .../data/repositories/PreferencesRepository.kt | 6 ++++++ .../VulcanNotificationListenerService.kt | 3 +++ app/src/main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 2 ++ .../res/xml/scheme_preferences_notifications.xml | 15 +++++++++++++-- 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt index 48eac48a2..e6437b167 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -133,6 +133,12 @@ class PreferencesRepository @Inject constructor( R.bool.pref_default_notification_piggyback ) + val isNotificationPiggybackRemoveOriginalEnabled: Boolean + get() = getBoolean( + R.string.pref_key_notifications_piggyback_cancel_original, + R.bool.pref_default_notification_piggyback_cancel_original + ) + val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug) val isDebugNotificationEnable: Boolean get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug) diff --git a/app/src/main/java/io/github/wulkanowy/services/piggyback/VulcanNotificationListenerService.kt b/app/src/main/java/io/github/wulkanowy/services/piggyback/VulcanNotificationListenerService.kt index c7df2dbc1..3c173495a 100644 --- a/app/src/main/java/io/github/wulkanowy/services/piggyback/VulcanNotificationListenerService.kt +++ b/app/src/main/java/io/github/wulkanowy/services/piggyback/VulcanNotificationListenerService.kt @@ -19,6 +19,9 @@ class VulcanNotificationListenerService : NotificationListenerService() { override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) { if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) { syncManager.startOneTimeSyncWorker() + if (preferenceRepository.isNotificationPiggybackRemoveOriginalEnabled) { + cancelNotification(statusBarNotification.key) + } } } } \ No newline at end of file diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index 7fb3d5c05..deeb36961 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -28,6 +28,7 @@ false 0 false + false LUCKY_NUMBER MESSAGES diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index fef062dd1..849d989ee 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -35,5 +35,6 @@ message_send_recipients last_sync_date notifications_piggyback + notifications_piggyback_cancel_original single_ad_support diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dff5babfd..bdf2935bd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -672,7 +672,9 @@ Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings. Show debug notifications Synchronization is disabled + Official app notifications Capture official app notifications + Remove official app notifications after capture Capture notifications With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY Upcoming lesson notifications diff --git a/app/src/main/res/xml/scheme_preferences_notifications.xml b/app/src/main/res/xml/scheme_preferences_notifications.xml index 442581bfd..0366914ac 100644 --- a/app/src/main/res/xml/scheme_preferences_notifications.xml +++ b/app/src/main/res/xml/scheme_preferences_notifications.xml @@ -16,9 +16,9 @@ app:title="@string/pref_notify_upcoming_lessons_switch" /> @@ -31,13 +31,24 @@ + app:title="@string/pref_notify_notifications_piggyback_header"> + + + Date: Mon, 27 Dec 2021 14:06:20 +0100 Subject: [PATCH 13/30] New Crowdin updates (#1729) --- app/src/main/res/values-cs/strings.xml | 13 +++++++++++++ app/src/main/res/values-de/strings.xml | 13 +++++++++++++ app/src/main/res/values-pl/strings.xml | 13 +++++++++++++ app/src/main/res/values-ru/strings.xml | 13 +++++++++++++ app/src/main/res/values-sk/strings.xml | 13 +++++++++++++ app/src/main/res/values-uk/strings.xml | 13 +++++++++++++ 6 files changed, 78 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 8e49d4f1c..330b75784 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -204,6 +204,17 @@ Další lekce Zobrazit další lekce Žádné informace o dalších lekcích + Nová lekce + Nová další lekce + Další lekce byla úspěšně přidána + Další lekce byla úspěšně odstraněna + Opakovat každý týden + Odstranit další lekci + Pouze tato lekce + Všechny v sérii + Čas zahájení + Čas ukončení + Čas ukončení musí být pozdější než čas zahájení Shrnutí frekvencí Neprítomnosť zo školských dôvodov @@ -668,7 +679,9 @@ Vaše zařízení může mít problémy se synchronizací dat as upozorněními.\n\nChcete-li je opravit, přidejte Wulkanového do funkce Autostart a vypněte optimalizaci/úsporu baterie v nastavení systému telefonu. Zobrazit upozornění o ladění Synchronizace je vypnutá + Official app notifications Zachytit upozornění oficiální aplikací + Remove official app notifications after capture Zachytit upozornění S touto funkcí můžete získat náhradu push upozornění jako v oficiální aplikaci. Vše, co musíte udělat, je povolit Wulkanowému číst všechna vaše upozornění v nastaveních systému.\n\nJak to funguje?\nKdyž obdržíte oznámení v Deníčku VULCAN, Wulkanowy bude o tom informován (k tomu je to dodatečné povolení) a spustí synchronizaci, aby mohl zaslat vlastní upozornění.\n\nPOUZE PRO POKROČILÉ UŽIVATELE Upozornění o nadcházející lekci diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 83bbe307f..9a6e3e662 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -182,6 +182,17 @@ Zusätzliche Lektionen Zusätzliche Lektionen anzeigen Keine Informationen über zusätzlichen Lektionen + New lesson + New additional lesson + Additional lesson added successfully + Additional lesson deleted successfully + Repeat weekly + Delete additional lesson + Just this lesson + All in the series + Start time + End time + End time must be greater than start time Übersicht über die Schulbesuch Aus schulischen Gründen abwesend @@ -582,7 +593,9 @@ Ihr Gerät hat möglicherweise Probleme mit der Datensynchronisierung und Benachrichtigungen.\n\nUm diese zu reparieren, fügen Sie Wulkanowy zum Autostart hinzu und deaktivieren Sie die Batterieoptimierung in den Systemeinstellungen des Geräts. Debug-Benachrichtigungen anzeigen Synchronisierung ist deaktiviert + Official app notifications Offizielle App-Benachrichtigungen erfassen + Remove official app notifications after capture Benachrichtigungen erfassen With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY Upcoming lesson notifications diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 17fcb0884..32862c01f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -204,6 +204,17 @@ Dodatkowe lekcje Pokaż dodatkowe lekcje Brak informacji o dodatkowych lekcjach + Nowa lekcja + Nowa dodatkowa lekcja + Dodatkowa lekcja dodana pomyślnie + Dodatkowa lekcja usunięta pomyślnie + Powtarzaj co tydzień + Usuń dodatkową lekcję + Tylko ta lekcja + Wszystkie w serii + Godzina rozpoczęcia + Godzina zakończenia + Godzina zakończenia musi być późniejsza niż godzina rozpoczęcia Podsumowanie frekwencji Nieobecność z przyczyn szkolnych @@ -668,7 +679,9 @@ Na twoim urządzeniu mogą występować problemy z synchronizacją danych i powiadomieniami.\n\nBy je naprawić, dodaj Wulkanowego do autostartu i wyłącz optymalizację/oszczędzanie baterii w ustawieniach systemowych telefonu. Pokazuj powiadomienia debugowania Synchronizacja jest wyłączona + Official app notifications Przechwytywanie powiadomień oficjalnej aplikacji + Remove official app notifications after capture Przechwytywanie powiadomień Dzięki tej funkcji możesz uzyskać namiastkę powiadomień push, takich jak w oficjalnej aplikacji. Wszystko, co musisz zrobić, to zezwolić Wulkanowemu na odczytywanie wszystkich powiadomień w ustawieniach systemowych.\n\nJak to działa?\nKiedy otrzymasz powiadomienie w Dzienniczku VULCAN, Wulkanowy zostanie o tym powiadomiony (do tego jest to dodatkowe uprawnienie) i uruchomi synchronizację, aby mógł wysłać własne powiadomienie.\n\nWYŁĄCZNIE DLA ZAAWANSOWANYCH UŻYTKOWNIKÓW Powiadomienia o nadchodzących lekcjach diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e09e6d7da..87cae3ea7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -204,6 +204,17 @@ Дополнительные уроки Показать дополнительные уроки Нет информации о дополнительных уроках + New lesson + New additional lesson + Additional lesson added successfully + Additional lesson deleted successfully + Repeat weekly + Delete additional lesson + Just this lesson + All in the series + Start time + End time + End time must be greater than start time Итоговая посещаемость Отсутствие по школьным причинам @@ -668,7 +679,9 @@ На вашем устройстве могут быть проблемы с синхронизацией данных и уведомлениями.\n\nЧтобы их исправить, вам необходимо добавить Wulkanowy в авто-старт и выключить оптимизацию/экономию батареи в настройках устройства. Показывать дебаг-уведомления Синхронизация отключена + Official app notifications Записывать официальные уведомления + Remove official app notifications after capture Показывать push-уведомления С помощью этой функции вы можете получить замену push-уведомлений, как в официальном приложении. Все, что вам нужно сделать, это разрешить Wulkanowy получать все уведомления в настройках системы.\n\nКак это работает?\nКогда вы получаете уведомление в Dziennik VULCAN, Wulkanowy будет уведомлен (это требует дополнительных прав) и запустит синхронизацию, чтобы отправить свое уведомление.\n\nТОЛЬКО ДЛЯ ПОЛЬЗОВАТЕЛЯ Показывать уведомления о будущих уроках diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 584a923d5..7899b5b59 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -204,6 +204,17 @@ Ďalšie lekcie Zobraziť ďalšie lekcie Žiadne informácie o ďalších lekciách + Nová lekcia + Nová ďalšia lekcia + Ďalšia lekcia bola úspešne pridaná + Ďalšia lekcia bola úspešne odstránená + Opakovať každý týždeň + Odstrániť ďalšiu lekciu + Iba táto lekcia + Všetky v sérii + Čas začatia + Čas ukončenia + Čas ukončenia musí byť neskorší ako čas začatia Zhrnutie frekvencií Neprítomnosť zo školských dôvodov @@ -668,7 +679,9 @@ Vaše zariadenie môže mať problémy so synchronizáciou dát as upozorneniami.\n\nAk ich chcete opraviť, pridajte Wulkanového do funkcie Autostart a vypnite optimalizáciu/úsporu batérie v nastavení systému telefóne. Zobraziť upozornenia o ladení Synchronizácia je vypnutá + Official app notifications Zachytiť upozornenia oficiálnej aplikácie + Remove official app notifications after capture Zachytiť upozornenia S touto funkciou môžete získať náhradu push upozornení ako v oficiálnej aplikácii. Všetko, čo musíte urobiť, je povoliť Wulkanowému čítať všetky vaše upozornenia v nastaveniach systému.\n\nAko to funguje?\nKeď dostanete oznámenie v Deníčku VULCAN, Wulkanowy bude o tom informovaný (k tomu je to dodatočné povolenie) a spustí synchronizáciu, aby mohol zaslať vlastné upozornenie.\n\nLEN PRE POKROČILÝCH POUŽĺVATEĹOV Upozornenia o nadchádzajúcej lekciu diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 5d24fedf2..f3f749cf9 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -204,6 +204,17 @@ Додаткові уроки Показати додаткові уроки Немає інформації про додаткових уроків + New lesson + New additional lesson + Additional lesson added successfully + Additional lesson deleted successfully + Repeat weekly + Delete additional lesson + Just this lesson + All in the series + Start time + End time + End time must be greater than start time Підсумок відвідуваності Відсутність зі шкільних причин @@ -668,7 +679,9 @@ На вашому пристрої можуть бути помилки з синхронізацією і повідомленнями\n\nЩоб виправити іх, вам необхідно додати Wulkanowy в авто-старт и вимкнути оптимізацію/экономію батареї в налаштуваннях пристрою. Показувати дебаг-повідомлення Синхронізація вимкнена + Official app notifications Захоплювати офіційні сповіщення програм + Remove official app notifications after capture Показувати push-повідомлення За допомогою цієї функції ви можете отримати заміну push -повідомлень, як у офіційному додатку. Все, що вам потрібно зробити, це дозволити Wulkanowy отримувати всі сповіщення у налаштуваннях вашої системи. \ N \ nЯк це працює? \ NКоли ви отримаєте сповіщення у Dziennik VULCAN, Wulkanowy отримає сповіщення (для цього призначені ці додаткові дозволи) і запустить синхронізація, яка може надсилати власне сповіщення. \ n \ n ТІЛЬКИ ДЛЯ РОЗШИРЕНИХ КОРИСТУВАЧІВ Показувати повідомлення о наступних уроках From 5e9691750843d9a295b88689ebbe2614322bd886 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Tue, 28 Dec 2021 12:16:52 +0100 Subject: [PATCH 14/30] Fix overlapping text in the error dialog (#1708) --- .../wulkanowy/services/sync/SyncWorker.kt | 42 +++--- .../github/wulkanowy/ui/base/ErrorDialog.kt | 112 ++++++++-------- .../modules/dashboard/DashboardPresenter.kt | 4 +- .../ui/modules/settings/sync/SyncFragment.kt | 2 +- .../ui/modules/settings/sync/SyncPresenter.kt | 5 +- app/src/main/res/layout/dialog_error.xml | 126 +++--------------- 6 files changed, 111 insertions(+), 180 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt index 52979e635..a2d1dd57e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt @@ -38,13 +38,18 @@ class SyncWorker @AssistedInject constructor( private val dispatchersProvider: DispatchersProvider ) : CoroutineWorker(appContext, workerParameters) { - override suspend fun doWork() = withContext(dispatchersProvider.io) { + override suspend fun doWork(): Result = withContext(dispatchersProvider.io) { Timber.i("SyncWorker is starting") if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure() - val student = studentRepository.getCurrentStudent() - val semester = semesterRepository.getCurrentSemester(student, true) + val (student, semester) = try { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student, true) + student to semester + } catch (e: Throwable) { + return@withContext getResultFromErrors(listOf(e)) + } val exceptions = works.mapNotNull { work -> try { @@ -62,20 +67,7 @@ class SyncWorker @AssistedInject constructor( } } } - val result = when { - exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> { - Result.failure( - Data.Builder() - .putString("error", exceptions.map { it.stackTraceToString() }.toString()) - .build() - ) - } - exceptions.isNotEmpty() -> Result.retry() - else -> { - preferencesRepository.lasSyncDate = LocalDateTime.now() - Result.success() - } - } + val result = getResultFromErrors(exceptions) if (preferencesRepository.isDebugNotificationEnable) notify(result) Timber.i("SyncWorker result: $result") @@ -83,6 +75,22 @@ class SyncWorker @AssistedInject constructor( return@withContext result } + private fun getResultFromErrors(errors: List): Result = when { + errors.isNotEmpty() && inputData.getBoolean("one_time", false) -> { + Result.failure( + Data.Builder() + .putString("error_message", errors.joinToString { it.message.toString() }) + .putString("error_stack", errors.map { it.stackTraceToString() }.toString()) + .build() + ) + } + errors.isNotEmpty() -> Result.retry() + else -> { + preferencesRepository.lasSyncDate = LocalDateTime.now() + Result.success() + } + } + private fun notify(result: Result) { notificationManager.notify( Random.nextInt(Int.MAX_VALUE), diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt index 4c279d816..c2ffff1f5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt @@ -1,28 +1,25 @@ package io.github.wulkanowy.ui.base +import android.app.Dialog import android.content.ClipData import android.content.ClipboardManager import android.os.Bundle import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.HorizontalScrollView import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.appcompat.app.AlertDialog import androidx.core.content.getSystemService +import androidx.core.os.bundleOf import androidx.core.view.isGone +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogErrorBinding import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.getString -import io.github.wulkanowy.utils.openAppInMarket -import io.github.wulkanowy.utils.openEmailClient -import io.github.wulkanowy.utils.openInternetBrowser +import io.github.wulkanowy.utils.* import okhttp3.internal.http2.StreamResetException import java.io.InterruptedIOException import java.net.ConnectException @@ -31,72 +28,77 @@ import java.net.UnknownHostException import javax.inject.Inject @AndroidEntryPoint -class ErrorDialog : BaseDialogFragment() { - - private lateinit var error: Throwable +class ErrorDialog : DialogFragment() { @Inject lateinit var appInfo: AppInfo companion object { - private const val ARGUMENT_KEY = "Data" + private const val ARGUMENT_KEY = "error" fun newInstance(error: Throwable) = ErrorDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, error) } + arguments = bundleOf(ARGUMENT_KEY to error) } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - error = getSerializable(ARGUMENT_KEY) as Throwable + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable + + val binding = DialogErrorBinding.inflate(LayoutInflater.from(context)) + binding.bindErrorDetails(error) + + return getAlertDialog(binding, error).apply { + enableReportButtonIfErrorIsReportable(error) } } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogErrorBinding.inflate(inflater).apply { binding = this }.root - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val errorStacktrace = error.stackTraceToString() - - with(binding) { - errorDialogContent.text = errorStacktrace.replace(": ${error.localizedMessage}", "") - with(errorDialogHorizontalScroll) { - post { fullScroll(HorizontalScrollView.FOCUS_LEFT) } - } - errorDialogCopy.setOnClickListener { - val clip = ClipData.newPlainText("Error details", errorStacktrace) - activity?.getSystemService()?.setPrimaryClip(clip) - - Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show() - } - errorDialogCancel.setOnClickListener { dismiss() } - errorDialogReport.setOnClickListener { + private fun getAlertDialog(binding: DialogErrorBinding, error: Throwable): AlertDialog { + return MaterialAlertDialogBuilder(requireContext()).apply { + val errorStacktrace = error.stackTraceToString() + setTitle(R.string.all_details) + setView(binding.root) + setNeutralButton(R.string.about_feedback) { _, _ -> openConfirmDialog { openEmailClient(errorStacktrace) } } + setNegativeButton(android.R.string.cancel) { _, _ -> } + setPositiveButton(android.R.string.copy) { _, _ -> copyErrorToClipboard(errorStacktrace) } + }.create() + } + + private fun DialogErrorBinding.bindErrorDetails(error: Throwable) { + return with(this) { errorDialogHumanizedMessage.text = resources.getString(error) errorDialogErrorMessage.text = error.localizedMessage errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank() - errorDialogReport.isEnabled = when (error) { - is UnknownHostException, - is InterruptedIOException, - is ConnectException, - is StreamResetException, - is SocketTimeoutException, - is ServiceUnavailableException, - is FeatureDisabledException, - is FeatureNotAvailableException -> false - else -> true - } + errorDialogContent.text = error.stackTraceToString() + .replace(": ${error.localizedMessage}", "") } } + private fun AlertDialog.enableReportButtonIfErrorIsReportable(error: Throwable) { + setOnShowListener { + getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = isErrorShouldBeReported(error) + } + } + + private fun isErrorShouldBeReported(error: Throwable): Boolean = when (error) { + is UnknownHostException, + is InterruptedIOException, + is ConnectException, + is StreamResetException, + is SocketTimeoutException, + is ServiceUnavailableException, + is FeatureDisabledException, + is FeatureNotAvailableException -> false + else -> true + } + + private fun copyErrorToClipboard(errorStacktrace: String) { + val clip = ClipData.newPlainText("Error details", errorStacktrace) + requireActivity().getSystemService()?.setPrimaryClip(clip) + Toast.makeText(requireContext(), R.string.all_copied, LENGTH_LONG).show() + } + private fun openConfirmDialog(callback: () -> Unit) { AlertDialog.Builder(requireContext()) .setTitle(R.string.dialog_error_check_update) @@ -127,4 +129,8 @@ class ErrorDialog : BaseDialogFragment() { } ) } + + private fun showMessage(text: String) { + Toast.makeText(requireContext(), text, LENGTH_LONG).show() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt index d081e19aa..7ba4c4b6f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt @@ -716,7 +716,7 @@ class DashboardPresenter @Inject constructor( itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null val isGeneralError = filteredItems.none { it.error == null } && filteredItems.isNotEmpty() || isAccountItemError - val errorMessage = itemsLoadedList.map { it.error?.stackTraceToString() }.toString() + val firstError = itemsLoadedList.mapNotNull { it.error }.firstOrNull() val filteredOriginalLoadedList = dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT } @@ -726,7 +726,7 @@ class DashboardPresenter @Inject constructor( filteredOriginalLoadedList.none { it.error == null } && filteredOriginalLoadedList.isNotEmpty() || wasAccountItemError if (isGeneralError && isItemsLoaded) { - lastError = Exception(errorMessage) + lastError = requireNotNull(firstError) view?.run { showProgress(false) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt index 160b7c37b..d81c35d39 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -91,7 +91,7 @@ class SyncFragment : PreferenceFragmentCompat(), } override fun showErrorDetailsDialog(error: Throwable) { - ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) + ErrorDialog.newInstance(error).show(childFragmentManager, "error_details") } override fun onResume() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt index 0d404a138..fc47e29ab 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt @@ -59,7 +59,10 @@ class SyncPresenter @Inject constructor( WorkInfo.State.FAILED -> { showError( syncFailedString, - Throwable(workInfo.outputData.getString("error")) + Throwable( + message = workInfo.outputData.getString("error_message"), + cause = Throwable(workInfo.outputData.getString("error_stack")) + ) ) analytics.logEvent("sync_now", "status" to "failed") } diff --git a/app/src/main/res/layout/dialog_error.xml b/app/src/main/res/layout/dialog_error.xml index b52c99aca..98b9c8b16 100644 --- a/app/src/main/res/layout/dialog_error.xml +++ b/app/src/main/res/layout/dialog_error.xml @@ -7,15 +7,6 @@ android:orientation="vertical" tools:context=".ui.base.ErrorDialog"> - - - + android:layout_height="200dp" + android:overScrollMode="ifContentScrolls" + android:paddingHorizontal="24dp" + app:layout_constraintTop_toTopOf="parent"> - + android:layout_height="wrap_content"> - - - - - - - - - - - - - - - - - - - + + + From 684c258e2d817525cd4a26ea23233ffbefb032c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 28 Dec 2021 18:56:59 +0100 Subject: [PATCH 15/30] Remove admin message offline first cache (#1735) --- .../repositories/AdminMessageRepository.kt | 13 +++------- .../modules/dashboard/DashboardPresenter.kt | 26 +++---------------- 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt index 1b17e3bf3..e455411ea 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt @@ -4,7 +4,6 @@ import io.github.wulkanowy.data.api.AdminMessageService import io.github.wulkanowy.data.db.dao.AdminMessageDao import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.networkBoundResource import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -14,23 +13,17 @@ import javax.inject.Singleton class AdminMessageRepository @Inject constructor( private val adminMessageService: AdminMessageService, private val adminMessageDao: AdminMessageDao, - private val appInfo: AppInfo, - private val refreshHelper: AutoRefreshHelper, + private val appInfo: AppInfo ) { private val saveFetchResultMutex = Mutex() - private val cacheKey = "admin_messages" - - suspend fun getAdminMessages(student: Student, forceRefresh: Boolean) = networkBoundResource( + suspend fun getAdminMessages(student: Student) = networkBoundResource( mutex = saveFetchResultMutex, query = { adminMessageDao.loadAll() }, fetch = { adminMessageService.getAdminMessages() }, - shouldFetch = { - refreshHelper.shouldBeRefreshed(cacheKey) || forceRefresh - }, + shouldFetch = { true }, saveFetchResult = { oldItems, newItems -> adminMessageDao.removeOldAndSaveNew(oldItems, newItems) - refreshHelper.updateLastRefreshTimestamp(cacheKey) }, showSavedOnLoading = false, mapResult = { adminMessages -> diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt index 7ba4c4b6f..80340c08a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt @@ -6,33 +6,13 @@ import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.enums.MessageFolder -import io.github.wulkanowy.data.repositories.AdminMessageRepository -import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository -import io.github.wulkanowy.data.repositories.ConferenceRepository -import io.github.wulkanowy.data.repositories.ExamRepository -import io.github.wulkanowy.data.repositories.GradeRepository -import io.github.wulkanowy.data.repositories.HomeworkRepository -import io.github.wulkanowy.data.repositories.LuckyNumberRepository -import io.github.wulkanowy.data.repositories.MessageRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository -import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository -import io.github.wulkanowy.data.repositories.SemesterRepository -import io.github.wulkanowy.data.repositories.StudentRepository -import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.data.repositories.* import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.nextOrSameSchoolDay -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.filterNot -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber import java.time.LocalDate @@ -582,7 +562,7 @@ class DashboardPresenter @Inject constructor( } private fun loadAdminMessage(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { adminMessageRepository.getAdminMessages(student, forceRefresh) } + flowWithResourceIn { adminMessageRepository.getAdminMessages(student) } .map { val isDismissed = it.data?.id in preferencesRepository.dismissedAdminMessageIds it.copy(data = it.data.takeUnless { isDismissed }) From 496641f594cdef4ab882dadeab03fa01b0908877 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Wed, 29 Dec 2021 08:31:43 +0100 Subject: [PATCH 16/30] Fix notification spam after login (#1715) --- .../io/github/wulkanowy/services/sync/SyncManager.kt | 4 +++- .../io/github/wulkanowy/services/sync/SyncWorker.kt | 7 ++++++- .../services/sync/works/AttendanceSummaryWork.kt | 9 +++++++-- .../wulkanowy/services/sync/works/AttendanceWork.kt | 6 ++---- .../services/sync/works/CompletedLessonWork.kt | 10 ++++++++-- .../wulkanowy/services/sync/works/ConferenceWork.kt | 6 ++---- .../github/wulkanowy/services/sync/works/ExamWork.kt | 6 ++---- .../services/sync/works/GradeStatisticsWork.kt | 2 +- .../github/wulkanowy/services/sync/works/GradeWork.kt | 6 ++---- .../wulkanowy/services/sync/works/HomeworkWork.kt | 6 ++---- .../wulkanowy/services/sync/works/LuckyNumberWork.kt | 6 ++---- .../wulkanowy/services/sync/works/MessageWork.kt | 6 ++---- .../github/wulkanowy/services/sync/works/NoteWork.kt | 6 ++---- .../wulkanowy/services/sync/works/RecipientWork.kt | 2 +- .../services/sync/works/SchoolAnnouncementWork.kt | 6 ++---- .../wulkanowy/services/sync/works/TeacherWork.kt | 2 +- .../wulkanowy/services/sync/works/TimetableWork.kt | 6 ++---- .../io/github/wulkanowy/services/sync/works/Work.kt | 2 +- .../login/studentselect/LoginStudentSelectPresenter.kt | 3 +++ .../studentselect/LoginStudentSelectPresenterTest.kt | 6 +++++- 20 files changed, 56 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt index 32ca20afc..c1bed4dd3 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt @@ -74,10 +74,12 @@ class SyncManager @Inject constructor( } } - fun startOneTimeSyncWorker(): Flow { + // if quiet, no notifications will be sent + fun startOneTimeSyncWorker(quiet: Boolean = false): Flow { val work = OneTimeWorkRequestBuilder() .setInputData( Data.Builder() + .putBoolean("quiet", quiet) .putBoolean("one_time", true) .build() ) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt index a2d1dd57e..4a9bfd58d 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt @@ -54,7 +54,7 @@ class SyncWorker @AssistedInject constructor( val exceptions = works.mapNotNull { work -> try { Timber.i("${work::class.java.simpleName} is starting") - work.doWork(student, semester) + work.doWork(student, semester, isNotificationsEnabled()) Timber.i("${work::class.java.simpleName} result: Success") null } catch (e: Throwable) { @@ -75,6 +75,11 @@ class SyncWorker @AssistedInject constructor( return@withContext result } + private fun isNotificationsEnabled(): Boolean { + val quiet = inputData.getBoolean("quiet", false) + return preferencesRepository.isNotificationsEnable && !quiet + } + private fun getResultFromErrors(errors: List): Result = when { errors.isNotEmpty() && inputData.getBoolean("one_time", false) -> { Result.failure( diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt index cbe1fe6bd..84b7017b1 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt @@ -10,7 +10,12 @@ class AttendanceSummaryWork @Inject constructor( private val attendanceSummaryRepository: AttendanceSummaryRepository ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { - attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, true).waitForResult() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + attendanceSummaryRepository.getAttendanceSummary( + student = student, + semester = semester, + subjectId = -1, + forceRefresh = true, + ).waitForResult() } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt index f7b680e31..9abf43e08 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.AttendanceRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification import io.github.wulkanowy.utils.previousOrSameSchoolDay import io.github.wulkanowy.utils.waitForResult @@ -14,17 +13,16 @@ import javax.inject.Inject class AttendanceWork @Inject constructor( private val attendanceRepository: AttendanceRepository, private val newAttendanceNotification: NewAttendanceNotification, - private val preferencesRepository: PreferencesRepository ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { attendanceRepository.getAttendance( student = student, semester = semester, start = now().previousOrSameSchoolDay, end = now().previousOrSameSchoolDay, forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify, ) .waitForResult() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt index 17bd61292..c6ada9446 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt @@ -13,7 +13,13 @@ class CompletedLessonWork @Inject constructor( private val completedLessonsRepository: CompletedLessonsRepository ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { - completedLessonsRepository.getCompletedLessons(student, semester, now().monday, now().sunday, true).waitForResult() + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + completedLessonsRepository.getCompletedLessons( + student = student, + semester = semester, + start = now().monday, + end = now().sunday, + forceRefresh = true, + ).waitForResult() } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt index 002b4f764..becd74668 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.ConferenceRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.notifications.NewConferenceNotification import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first @@ -11,16 +10,15 @@ import javax.inject.Inject class ConferenceWork @Inject constructor( private val conferenceRepository: ConferenceRepository, - private val preferencesRepository: PreferencesRepository, private val newConferenceNotification: NewConferenceNotification, ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { conferenceRepository.getConferences( student = student, semester = semester, forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify ).waitForResult() conferenceRepository.getConferenceFromDatabase(semester).first() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt index a1ce553a7..39579dc8c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.ExamRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.notifications.NewExamNotification import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first @@ -12,18 +11,17 @@ import javax.inject.Inject class ExamWork @Inject constructor( private val examRepository: ExamRepository, - private val preferencesRepository: PreferencesRepository, private val newExamNotification: NewExamNotification, ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { examRepository.getExams( student = student, semester = semester, start = now(), end = now(), forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify, ).waitForResult() examRepository.getExamsFromDatabase(semester, now()).first() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt index 4575b419b..2e915199e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt @@ -10,7 +10,7 @@ class GradeStatisticsWork @Inject constructor( private val gradeStatisticsRepository: GradeStatisticsRepository ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { with(gradeStatisticsRepository) { getGradesPartialStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() getGradesSemesterStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt index 0932405eb..dd49f143c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.GradeRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.notifications.NewGradeNotification import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first @@ -11,16 +10,15 @@ import javax.inject.Inject class GradeWork @Inject constructor( private val gradeRepository: GradeRepository, - private val preferencesRepository: PreferencesRepository, private val newGradeNotification: NewGradeNotification, ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { gradeRepository.getGrades( student = student, semester = semester, forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify, ).waitForResult() gradeRepository.getGradesFromDatabase(semester).first() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt index 2a5d2d7c0..1385191b7 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.HomeworkRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.waitForResult @@ -13,18 +12,17 @@ import javax.inject.Inject class HomeworkWork @Inject constructor( private val homeworkRepository: HomeworkRepository, - private val preferencesRepository: PreferencesRepository, private val newHomeworkNotification: NewHomeworkNotification, ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { homeworkRepository.getHomework( student = student, semester = semester, start = now().nextOrSameSchoolDay, end = now().nextOrSameSchoolDay, forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify, ).waitForResult() homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt index 348f92142..f223a8546 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt @@ -3,22 +3,20 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.LuckyNumberRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.notifications.NewLuckyNumberNotification import io.github.wulkanowy.utils.waitForResult import javax.inject.Inject class LuckyNumberWork @Inject constructor( private val luckyNumberRepository: LuckyNumberRepository, - private val preferencesRepository: PreferencesRepository, private val newLuckyNumberNotification: NewLuckyNumberNotification, ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { luckyNumberRepository.getLuckyNumber( student = student, forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify, ).waitForResult() luckyNumberRepository.getNotNotifiedLuckyNumber(student)?.let { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt index b5624a76d..5bf326c7b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt @@ -4,7 +4,6 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.repositories.MessageRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.notifications.NewMessageNotification import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first @@ -12,17 +11,16 @@ import javax.inject.Inject class MessageWork @Inject constructor( private val messageRepository: MessageRepository, - private val preferencesRepository: PreferencesRepository, private val newMessageNotification: NewMessageNotification, ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { messageRepository.getMessages( student = student, semester = semester, folder = RECEIVED, forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify ).waitForResult() messageRepository.getMessagesFromDatabase(student).first() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt index 6f18eddf1..d66c3d661 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.NoteRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.notifications.NewNoteNotification import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first @@ -11,16 +10,15 @@ import javax.inject.Inject class NoteWork @Inject constructor( private val noteRepository: NoteRepository, - private val preferencesRepository: PreferencesRepository, private val newNoteNotification: NewNoteNotification, ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { noteRepository.getNotes( student = student, semester = semester, forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify, ).waitForResult() noteRepository.getNotesFromDatabase(student).first() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt index 34ab3db04..425e68b91 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt @@ -11,7 +11,7 @@ class RecipientWork @Inject constructor( private val recipientRepository: RecipientRepository ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { reportingUnitRepository.refreshReportingUnits(student) reportingUnitRepository.getReportingUnits(student).let { units -> diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt index 268992f46..9cee59024 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt @@ -2,7 +2,6 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification import io.github.wulkanowy.utils.waitForResult @@ -11,15 +10,14 @@ import javax.inject.Inject class SchoolAnnouncementWork @Inject constructor( private val schoolAnnouncementRepository: SchoolAnnouncementRepository, - private val preferencesRepository: PreferencesRepository, private val newSchoolAnnouncementNotification: NewSchoolAnnouncementNotification, ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { schoolAnnouncementRepository.getSchoolAnnouncements( student = student, forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify, ).waitForResult() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt index 7c614c6c5..751fb6cc7 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt @@ -8,7 +8,7 @@ import javax.inject.Inject class TeacherWork @Inject constructor(private val teacherRepository: TeacherRepository) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { teacherRepository.getTeachers(student, semester, true).waitForResult() } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt index fcc330638..575f9b961 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt @@ -2,7 +2,6 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification import io.github.wulkanowy.utils.nextOrSameSchoolDay @@ -14,17 +13,16 @@ import javax.inject.Inject class TimetableWork @Inject constructor( private val timetableRepository: TimetableRepository, private val changeTimetableNotification: ChangeTimetableNotification, - private val preferencesRepository: PreferencesRepository ) : Work { - override suspend fun doWork(student: Student, semester: Semester) { + override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { timetableRepository.getTimetable( student = student, semester = semester, start = now().nextOrSameSchoolDay, end = now().nextOrSameSchoolDay, forceRefresh = true, - notify = preferencesRepository.isNotificationsEnable + notify = notify, ) .waitForResult() diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt index c41f41ce2..1c0214cdd 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/Work.kt @@ -5,5 +5,5 @@ import io.github.wulkanowy.data.db.entities.Student interface Work { - suspend fun doWork(student: Student, semester: Semester) + suspend fun doWork(student: Student, semester: Semester, notify: Boolean) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index 8c475a672..71c60e62d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -4,6 +4,7 @@ import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper @@ -16,6 +17,7 @@ import javax.inject.Inject class LoginStudentSelectPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, + private val syncManager: SyncManager, private val analytics: AnalyticsHelper ) : BasePresenter(loginErrorHandler, studentRepository) { @@ -97,6 +99,7 @@ class LoginStudentSelectPresenter @Inject constructor( } Status.SUCCESS -> { Timber.i("Registration result: Success") + syncManager.startOneTimeSyncWorker(quiet = true) view?.openMainView() logRegisterEvent(studentsWithSemesters) } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt index 3be30827a..1ec885904 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt @@ -4,6 +4,7 @@ import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.mockk.MockKAnnotations @@ -36,6 +37,9 @@ class LoginStudentSelectPresenterTest { @MockK(relaxed = true) lateinit var analytics: AnalyticsHelper + @MockK(relaxed = true) + lateinit var syncManager: SyncManager + private lateinit var presenter: LoginStudentSelectPresenter private val testStudent by lazy { @@ -77,7 +81,7 @@ class LoginStudentSelectPresenterTest { every { loginStudentSelectView.showProgress(any()) } just Runs every { loginStudentSelectView.showContent(any()) } just Runs - presenter = LoginStudentSelectPresenter(studentRepository, errorHandler, analytics) + presenter = LoginStudentSelectPresenter(studentRepository, errorHandler, syncManager, analytics) presenter.onAttachView(loginStudentSelectView, emptyList()) } From 0965d03f1a664bd4de69e58d3ad88ddee415be8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 29 Dec 2021 11:57:17 +0100 Subject: [PATCH 17/30] New Crowdin updates (#1734) --- app/src/main/res/values-cs/strings.xml | 42 +++++++++++++------------- app/src/main/res/values-pl/strings.xml | 4 +-- app/src/main/res/values-sk/strings.xml | 40 ++++++++++++------------ 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 330b75784..3d9cdd9a7 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -12,7 +12,7 @@ O aplikaci Prohlížeč protokolů Ladění - Ladění upozornění + Ladění oznámení Tvůrci Licence Zprávy @@ -25,7 +25,7 @@ Podrobnosti účtu Informace o žáku Domů - Centrum upozornění + Centrum oznámení Semestr %1$d, %2$d/%3$d @@ -668,23 +668,23 @@ Známky barevné schéma Třídění předmětů Jazyk - Upozornění + Oznámení Jiné - Zobrazit upozornění - Zobrazit upozornění o nadcházející lekci - Nastavit upozornění o nadcházející lekci jako trvalé - Vypnout, když upozornění není ve vašem hodinkách/náramku viditelné - Otevřít systémová nastavení upozornění - Opravte problémy se synchronizací a upozorněním - Vaše zařízení může mít problémy se synchronizací dat as upozorněními.\n\nChcete-li je opravit, přidejte Wulkanového do funkce Autostart a vypněte optimalizaci/úsporu baterie v nastavení systému telefonu. - Zobrazit upozornění o ladění + Zobrazit oznámení + Zobrazit oznámení o nadcházející lekci + Nastavit oznámení o nadcházející lekci jako trvalé + Vypnout, když oznámení není ve vašem hodinkách/náramku viditelné + Otevřít systémová nastavení oznámení + Opravte problémy se synchronizací a oznámením + Vaše zařízení může mít problémy se synchronizací dat as oznámeními.\n\nChcete-li je opravit, přidejte Wulkanového do funkce Autostart a vypněte optimalizaci/úsporu baterie v nastavení systému telefonu. + Zobrazit oznámení o ladění Synchronizace je vypnutá - Official app notifications - Zachytit upozornění oficiální aplikací - Remove official app notifications after capture - Zachytit upozornění - S touto funkcí můžete získat náhradu push upozornění jako v oficiální aplikaci. Vše, co musíte udělat, je povolit Wulkanowému číst všechna vaše upozornění v nastaveních systému.\n\nJak to funguje?\nKdyž obdržíte oznámení v Deníčku VULCAN, Wulkanowy bude o tom informován (k tomu je to dodatečné povolení) a spustí synchronizaci, aby mohl zaslat vlastní upozornění.\n\nPOUZE PRO POKROČILÉ UŽIVATELE - Upozornění o nadcházející lekci + Oznámení oficiální aplikace + Zachytit oznámení oficiální aplikací + Odstranit oznámení oficiální aplikace po zachycení + Zachytit oznámení + S touto funkcí můžete získat náhradu push oznámení jako v oficiální aplikaci. Vše, co musíte udělat, je povolit Wulkanowému číst všechna vaše oznámení v nastaveních systému.\n\nJak to funguje?\nKdyž obdržíte oznámení v Deníčku VULCAN, Wulkanowy bude o tom informován (k tomu je to dodatečné povolení) a spustí synchronizaci, aby mohl zaslat vlastní oznámení.\n\nPOUZE PRO POKROČILÉ UŽIVATELE + Oznámení o nadcházející lekci Musíte povolit Wulkanovému nastavit budíky a připomenutí v nastavení vašeho systému pro použití této funkce. Přejít do nastavení Synchronizace @@ -711,7 +711,7 @@ Děkujeme za vaši podporu, vraťte se později pro více reklam Pokročilé Vzhled a chování - Upozornění + Oznámení Synchronizace Reklamy Známky @@ -724,8 +724,8 @@ Zprávy Vzhled a chování Jazyky, motivy, třídění předmětů - Upozornění aplikace, oprava problémů - Upozornění + Oznámení aplikací, oprava problémů + Oznámení Synchronizace Automatická aktualizace, interval aktualizací Hodnota plusu a mínusu, výpočet průměru @@ -741,7 +741,7 @@ Nové zprávy Nové poznámky Nové školní oznámení - Push upozornění + Push oznámení Nadcházející lekce Ladění Změny plánu lekcí diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 32862c01f..ff21b6446 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -679,9 +679,9 @@ Na twoim urządzeniu mogą występować problemy z synchronizacją danych i powiadomieniami.\n\nBy je naprawić, dodaj Wulkanowego do autostartu i wyłącz optymalizację/oszczędzanie baterii w ustawieniach systemowych telefonu. Pokazuj powiadomienia debugowania Synchronizacja jest wyłączona - Official app notifications + Powiadomienia oficjalnej aplikacji Przechwytywanie powiadomień oficjalnej aplikacji - Remove official app notifications after capture + Usuwaj powiadomienia oficjalnej aplikacji po przechwyceniu Przechwytywanie powiadomień Dzięki tej funkcji możesz uzyskać namiastkę powiadomień push, takich jak w oficjalnej aplikacji. Wszystko, co musisz zrobić, to zezwolić Wulkanowemu na odczytywanie wszystkich powiadomień w ustawieniach systemowych.\n\nJak to działa?\nKiedy otrzymasz powiadomienie w Dzienniczku VULCAN, Wulkanowy zostanie o tym powiadomiony (do tego jest to dodatkowe uprawnienie) i uruchomi synchronizację, aby mógł wysłać własne powiadomienie.\n\nWYŁĄCZNIE DLA ZAAWANSOWANYCH UŻYTKOWNIKÓW Powiadomienia o nadchodzących lekcjach diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 7899b5b59..3698fce92 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -12,7 +12,7 @@ O aplikácii Prehliadač protokolov Ladenie - Ladenie upozornení + Ladenie oznámení Tvorcovia Licencie Správy @@ -25,7 +25,7 @@ Podrobnosti účtu Informácie o žiakovi Domov - Centrum upozornení + Centrum oznámení Semester %1$d, %2$d/%3$d @@ -668,23 +668,23 @@ Známky farebnú schému Triedenie predmetov Jazyk - Upozornenia + Oznámenia Iné - Zobraziť upozornenia - Zobraziť upozornenia o nadchádzajúcej lekciu - Nastaviť upozornenia o nadchádzajúcej lekcii ako trvalé - Vypnúť, keď upozornenia nie je vo vašom hodinkách/náramku viditeľné - Otvoriť systémové nastavenia upozornení - Opravte problémy so synchronizáciou a upozornením - Vaše zariadenie môže mať problémy so synchronizáciou dát as upozorneniami.\n\nAk ich chcete opraviť, pridajte Wulkanového do funkcie Autostart a vypnite optimalizáciu/úsporu batérie v nastavení systému telefóne. - Zobraziť upozornenia o ladení + Zobraziť oznámenia + Zobraziť oznámenia o nadchádzajúcich lekciách + Nastaviť oznámenia o nadchádzajúcej lekcií ako trvalé + Vypnúť, keď oznámenia nie je vo vašom hodinkách/náramku viditeľné + Otvoriť systémové nastavenia oznámení + Opravte problémy so synchronizáciou a oznámeniami + Vaše zariadenie môže mať problémy so synchronizáciou dát as oznámeniami.\n\nAk ich chcete opraviť, pridajte Wulkanového do funkcie Autostart a vypnite optimalizáciu/úsporu batérie v nastavení systému telefóne. + Zobraziť oznámenia o ladení Synchronizácia je vypnutá - Official app notifications + Oznámenia oficiálnej aplikácie Zachytiť upozornenia oficiálnej aplikácie - Remove official app notifications after capture - Zachytiť upozornenia - S touto funkciou môžete získať náhradu push upozornení ako v oficiálnej aplikácii. Všetko, čo musíte urobiť, je povoliť Wulkanowému čítať všetky vaše upozornenia v nastaveniach systému.\n\nAko to funguje?\nKeď dostanete oznámenie v Deníčku VULCAN, Wulkanowy bude o tom informovaný (k tomu je to dodatočné povolenie) a spustí synchronizáciu, aby mohol zaslať vlastné upozornenie.\n\nLEN PRE POKROČILÝCH POUŽĺVATEĹOV - Upozornenia o nadchádzajúcej lekciu + Odstrániť oznámenia oficiálnej aplikácie po zachytení + Zachytiť oznámení + S touto funkciou môžete získať náhradu push oznámení ako v oficiálnej aplikácii. Všetko, čo musíte urobiť, je povoliť Wulkanowému čítať všetky vaše oznámenia v nastaveniach systému.\n\nAko to funguje?\nKeď dostanete oznámenie v Deníčku VULCAN, Wulkanowy bude o tom informovaný (k tomu je to dodatočné povolenie) a spustí synchronizáciu, aby mohol zaslať vlastné oznámenie.\n\nLEN PRE POKROČILÝCH POUŽĺVATEĹOV + Oznámenia o nadchádzajúcej lekcií Musíte povoliť Wulkanovému nastaviť budíky a pripomenutie v nastavení vášho systému pre použitie tejto funkcie. Prejsť do nastavení Synchronizácia @@ -711,7 +711,7 @@ Ďakujeme za vašu podporu, vráťte sa neskôr pre viac reklám Pokročilé Vzhľad a správanie - Upozornenia + Oznámenia Synchronizácia Reklamy Známky @@ -724,8 +724,8 @@ Správy Vzhľad a správanie Jazyky, motívy, triedenie predmetov - Upozornenia aplikácie, oprava problémov - Upozornenia + Oznámenia aplikácie, oprava problémov + Oznámenia Synchronizácia Automatická aktualizácia, interval aktualizácií Hodnota plusu a mínusu, výpočet priemeru @@ -741,7 +741,7 @@ Nové správy Nové poznámky Nové školské oznámenia - Push upozornenia + Push oznámenia Nadchádzajúce lekcie Ladenie Zmeny plánu lekcií From 68f0ecc45c7f29fb5ca2f1883be927129d10b5ff Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Wed, 29 Dec 2021 15:44:43 +0100 Subject: [PATCH 18/30] If only one student exists, don't show student name in timetable notification (#1711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Borcz --- .../wulkanowy/data/db/dao/StudentDao.kt | 7 +-- .../data/repositories/StudentRepository.kt | 3 + .../alarm/TimetableNotificationReceiver.kt | 55 ++++++++++++------- .../notifications/AppNotificationManager.kt | 9 +-- 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index 3dda8a44b..853a7cb74 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -1,12 +1,7 @@ package io.github.wulkanowy.data.db.dao -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert +import androidx.room.* import androidx.room.OnConflictStrategy.ABORT -import androidx.room.Query -import androidx.room.Transaction -import androidx.room.Update import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentWithSemesters diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt index 9e4a1aabc..570f8bdb9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt @@ -128,4 +128,7 @@ class StudentRepository @Inject constructor( suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) = studentDb.update(studentNickAndAvatar) + + suspend fun isOneUniqueStudent() = getSavedStudents(false) + .distinctBy { it.student.studentName }.size == 1 } diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt index b388d2ac5..167060181 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -41,6 +41,8 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { const val NOTIFICATION_TYPE_UPCOMING = 2 const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3 + // FIXME only shows one notification even if there are multiple students. + // Probably want to fix after #721 is merged. const val NOTIFICATION_ID = 2137 const val STUDENT_NAME = "student_name" @@ -60,16 +62,21 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { Timber.d("Receiving intent... ${intent.toUri(0)}") flowWithResource { + val showStudentName = !studentRepository.isOneUniqueStudent() val student = studentRepository.getCurrentStudent(false) val studentId = intent.getIntExtra(STUDENT_ID, 0) - if (student.studentId == studentId) prepareNotification(context, intent) - else Timber.d("Notification studentId($studentId) differs from current(${student.studentId})") + + if (student.studentId == studentId) { + prepareNotification(context, intent, showStudentName) + } else { + Timber.d("Notification studentId($studentId) differs from current(${student.studentId})") + } }.onEach { if (it.status == Status.ERROR) Timber.e(it.error!!) }.launchIn(GlobalScope) } - private fun prepareNotification(context: Context, intent: Intent) { + private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) { val type = intent.getIntExtra(LESSON_TYPE, 0) val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent @@ -78,7 +85,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { } val studentId = intent.getIntExtra(STUDENT_ID, 0) - val studentName = intent.getStringExtra(STUDENT_NAME) + val studentName = intent.getStringExtra(STUDENT_NAME).takeIf { showStudentName } val subject = intent.getStringExtra(LESSON_TITLE) val room = intent.getStringExtra(LESSON_ROOM) @@ -91,19 +98,26 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId") - showNotification( - context, isPersistent, studentName, - if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start, + val notificationTitleResId = + if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next + val notificationTitle = + context.getString(notificationTitleResId, "($room) $subject".removePrefix("()")) + + val nextLessonText = nextSubject?.let { context.getString( - if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, - "($room) $subject".removePrefix("()") - ), - nextSubject?.let { - context.getString( - R.string.timetable_later, - "($nextRoom) $nextSubject".removePrefix("()") - ) - } + R.string.timetable_later, + "($nextRoom) $nextSubject".removePrefix("()") + ) + } + + showNotification( + context = context, + isPersistent = isPersistent, + studentName = studentName, + countDown = if (type == NOTIFICATION_TYPE_CURRENT) end else start, + timeout = end - start, + title = notificationTitle, + next = nextLessonText ) } @@ -130,10 +144,11 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { .setTimeoutAfter(timeout) .setSmallIcon(R.drawable.ic_stat_timetable) .setColor(context.getCompatColor(R.color.colorPrimary)) - .setStyle(NotificationCompat.InboxStyle().also { - it.setSummaryText(studentName) - it.addLine(next) - }) + .setStyle(NotificationCompat.InboxStyle() + .addLine(next) + .also { inboxStyle -> + studentName?.let { inboxStyle.setSummaryText(it) } + }) .setContentIntent( PendingIntent.getActivity( context, diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt index da8d094c6..6fd6c1602 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt @@ -57,7 +57,7 @@ class AppNotificationManager @Inject constructor( NotificationCompat.BigTextStyle() .bigText(notificationData.content) .also { builder -> - if (shouldShowStudentName()) { + if (!studentRepository.isOneUniqueStudent()) { builder.setSummaryText(student.nickOrName) } } @@ -102,7 +102,7 @@ class AppNotificationManager @Inject constructor( NotificationCompat.BigTextStyle() .bigText(notificationData.content) .also { builder -> - if (shouldShowStudentName()) { + if (!studentRepository.isOneUniqueStudent()) { builder.setSummaryText(student.nickOrName) } } @@ -134,7 +134,7 @@ class AppNotificationManager @Inject constructor( .setStyle( NotificationCompat.InboxStyle() .also { builder -> - if (shouldShowStudentName()) { + if (!studentRepository.isOneUniqueStudent()) { builder.setSummaryText(student.nickOrName) } groupNotificationData.notificationDataList.forEach { @@ -174,7 +174,4 @@ class AppNotificationManager @Inject constructor( notificationRepository.saveNotification(notificationEntity) } - - private suspend fun shouldShowStudentName(): Boolean = - studentRepository.getSavedStudents(decryptPass = false).size > 1 } From 210c3a0e28d2fbbfdffcdf3b6ff55eb4ee3817ef Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Thu, 30 Dec 2021 12:50:06 +0100 Subject: [PATCH 19/30] Remove HiltBroadcastReceiver (#1736) --- .../github/wulkanowy/services/HiltBroadcastReceiver.kt | 9 --------- .../services/alarm/TimetableNotificationReceiver.kt | 5 ++--- .../modules/timetablewidget/TimetableWidgetProvider.kt | 5 ++--- 3 files changed, 4 insertions(+), 15 deletions(-) delete mode 100644 app/src/main/java/io/github/wulkanowy/services/HiltBroadcastReceiver.kt diff --git a/app/src/main/java/io/github/wulkanowy/services/HiltBroadcastReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/HiltBroadcastReceiver.kt deleted file mode 100644 index 1e795d439..000000000 --- a/app/src/main/java/io/github/wulkanowy/services/HiltBroadcastReceiver.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.wulkanowy.services - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent - -abstract class HiltBroadcastReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) {} -} diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt index 167060181..b4786703a 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.services.alarm import android.app.PendingIntent +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.os.Build @@ -12,7 +13,6 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository -import io.github.wulkanowy.services.HiltBroadcastReceiver import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.splash.SplashActivity @@ -28,7 +28,7 @@ import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint -class TimetableNotificationReceiver : HiltBroadcastReceiver() { +class TimetableNotificationReceiver : BroadcastReceiver() { @Inject lateinit var studentRepository: StudentRepository @@ -58,7 +58,6 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { @OptIn(DelicateCoroutinesApi::class) override fun onReceive(context: Context, intent: Intent) { - super.onReceive(context, intent) Timber.d("Receiving intent... ${intent.toUri(0)}") flowWithResource { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 641d22612..07e717eaf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager.* +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK @@ -18,7 +19,6 @@ import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.repositories.StudentRepository -import io.github.wulkanowy.services.HiltBroadcastReceiver import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.splash.SplashActivity @@ -33,7 +33,7 @@ import java.time.ZoneOffset import javax.inject.Inject @AndroidEntryPoint -class TimetableWidgetProvider : HiltBroadcastReceiver() { +class TimetableWidgetProvider : BroadcastReceiver() { @Inject lateinit var appWidgetManager: AppWidgetManager @@ -78,7 +78,6 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { @OptIn(DelicateCoroutinesApi::class) override fun onReceive(context: Context, intent: Intent) { - super.onReceive(context, intent) GlobalScope.launch { when (intent.action) { ACTION_APPWIDGET_UPDATE -> onUpdate(context, intent) From ba02531aa44e6da590d1573ee6d1040d785c1c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 31 Dec 2021 09:40:15 +0100 Subject: [PATCH 20/30] Fix timetable widget crash when there are no lessons for the day (#1737) --- .../modules/timetablewidget/TimetableWidgetFactory.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index 51b790e88..411fa6625 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -73,13 +73,12 @@ class TimetableWidgetFactory( updateTheme(appWidgetId) lessons = getLessons(date, studentId) - if (date == LocalDate.now()) { - val todayLastLessonEndTimestamp = - lessons.maxOf { it.end }.toEpochSecond(ZoneOffset.UTC) + val todayLastLessonEndTimestamp = lessons.maxOfOrNull { it.end } + if (date == LocalDate.now() && todayLastLessonEndTimestamp != null) { sharedPref.putLong( - getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), - todayLastLessonEndTimestamp, - true + key = getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), + value = todayLastLessonEndTimestamp.toEpochSecond(ZoneOffset.UTC), + sync = true ) } } From bfd7f688ab099f5a6ec105b483ee528ebdbf614a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 31 Dec 2021 11:24:02 +0100 Subject: [PATCH 21/30] Move webview locale fix to account recovery fragment (#1739) --- .../java/io/github/wulkanowy/WulkanowyApp.kt | 22 ++----------------- .../login/recover/LoginRecoverFragment.kt | 18 +++++++++++++++ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index 7cdeb622a..b5103e3ec 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -1,10 +1,7 @@ package io.github.wulkanowy import android.app.Application -import android.util.Log.DEBUG -import android.util.Log.INFO -import android.util.Log.VERBOSE -import android.webkit.WebView +import android.util.Log.* import androidx.hilt.work.HiltWorkerFactory import androidx.work.Configuration import com.yariksoffice.lingver.Lingver @@ -12,12 +9,7 @@ import dagger.hilt.android.HiltAndroidApp import fr.bipi.tressence.file.FileLoggerTree import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.base.ThemeManager -import io.github.wulkanowy.utils.ActivityLifecycleLogger -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.CrashLogExceptionTree -import io.github.wulkanowy.utils.CrashLogTree -import io.github.wulkanowy.utils.DebugLogTree +import io.github.wulkanowy.utils.* import timber.log.Timber import javax.inject.Inject @@ -44,7 +36,6 @@ class WulkanowyApp : Application(), Configuration.Provider { initializeAppLanguage() themeManager.applyDefaultTheme() initLogging() - fixWebViewLocale() } private fun initLogging() { @@ -76,15 +67,6 @@ class WulkanowyApp : Application(), Configuration.Provider { } } - private fun fixWebViewLocale() { - //https://stackoverflow.com/questions/40398528/android-webview-language-changes-abruptly-on-android-7-0-and-above - try { - WebView(this).destroy() - } catch (e: Throwable) { - //Ignore exceptions - } - } - override fun getWorkManagerConfiguration() = Configuration.Builder() .setWorkerFactory(workerFactory) .setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index fe32a14f1..c1c111d46 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -11,8 +11,10 @@ import android.webkit.WebView import android.webkit.WebViewClient import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged +import com.yariksoffice.lingver.Lingver import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentLoginRecoverBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity @@ -32,6 +34,12 @@ class LoginRecoverFragment : @Inject lateinit var presenter: LoginRecoverPresenter + @Inject + lateinit var lingver: Lingver + + @Inject + lateinit var preferencesRepository: PreferencesRepository + companion object { fun newInstance() = LoginRecoverFragment() } @@ -64,10 +72,20 @@ class LoginRecoverFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + restoreCorrectLocale() _binding = FragmentLoginRecoverBinding.bind(view) presenter.onAttachView(this) } + // https://issuetracker.google.com/issues/37113860 + private fun restoreCorrectLocale() { + if (preferencesRepository.appLanguage == "system") { + lingver.setFollowSystemLocale(requireContext()) + } else { + lingver.setLocale(requireContext(), lingver.getLocale()) + } + } + override fun initView() { (requireActivity() as LoginActivity).showActionBar(true) From e6b2acabd53753311cff09b5adb6da6e5a25517f Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Fri, 31 Dec 2021 11:53:09 +0100 Subject: [PATCH 22/30] Block app timezone to polish timezone (#1598) --- app/build.gradle | 2 +- .../46.json | 2430 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 47 +- .../io/github/wulkanowy/data/db/Converters.kt | 21 +- .../wulkanowy/data/db/dao/ConferenceDao.kt | 4 +- .../wulkanowy/data/db/entities/Conference.kt | 4 +- .../data/db/entities/GradeSummary.kt | 6 +- .../wulkanowy/data/db/entities/Message.kt | 4 +- .../data/db/entities/MobileDevice.kt | 4 +- .../data/db/entities/Notification.kt | 4 +- .../wulkanowy/data/db/entities/Student.kt | 4 +- .../wulkanowy/data/db/entities/Timetable.kt | 6 +- .../data/db/entities/TimetableAdditional.kt | 8 +- .../data/db/migrations/Migration12.kt | 12 +- .../data/db/migrations/Migration13.kt | 24 +- .../data/db/migrations/Migration27.kt | 24 +- .../data/db/migrations/Migration35.kt | 20 +- .../data/db/migrations/Migration46.kt | 102 + .../data/mappers/ConferenceMapper.kt | 2 +- .../wulkanowy/data/mappers/MessageMapper.kt | 4 +- .../data/mappers/MobileDeviceMapper.kt | 4 +- .../wulkanowy/data/mappers/StudentMapper.kt | 4 +- .../wulkanowy/data/mappers/TimetableMapper.kt | 8 +- .../data/repositories/ConferenceRepository.kt | 12 +- .../data/repositories/GradeRepository.kt | 20 +- .../data/repositories/NoteRepository.kt | 6 +- .../repositories/PreferencesRepository.kt | 27 +- .../alarm/TimetableNotificationReceiver.kt | 3 +- .../TimetableNotificationSchedulerHelper.kt | 22 +- .../wulkanowy/services/sync/SyncWorker.kt | 4 +- .../notifications/AppNotificationManager.kt | 4 +- .../ChangeTimetableNotification.kt | 4 +- .../NewConferenceNotification.kt | 4 +- .../ui/modules/about/AboutFragment.kt | 11 +- .../ui/modules/dashboard/DashboardAdapter.kt | 10 +- .../modules/dashboard/DashboardPresenter.kt | 4 +- .../debug/notification/mock/conference.kt | 5 +- .../debug/notification/mock/message.kt | 4 +- .../debug/notification/mock/timetable.kt | 7 +- .../ui/modules/main/MainPresenter.kt | 7 +- .../ui/modules/settings/sync/SyncPresenter.kt | 4 +- .../ui/modules/timetable/TimetableAdapter.kt | 13 +- .../ui/modules/timetable/TimetableDialog.kt | 4 +- .../add/AdditionalLessonAddPresenter.kt | 10 +- .../timetablewidget/TimetableWidgetFactory.kt | 3 +- .../utils/MaterialDatePickerUtils.kt | 9 +- .../io/github/wulkanowy/utils/RefreshUtils.kt | 9 +- .../wulkanowy/utils/ResourcesExtension.kt | 7 +- .../github/wulkanowy/utils/TimeExtension.kt | 42 +- .../wulkanowy/utils/TimetableExtension.kt | 6 +- .../io/github/wulkanowy/TestEnityCreator.kt | 2 +- .../data/db/migrations/Migration13Test.kt | 34 +- .../data/repositories/GradeRepositoryTest.kt | 78 +- .../repositories/MessageRepositoryTest.kt | 21 +- .../MobileDeviceRepositoryTest.kt | 5 +- .../repositories/TimetableRepositoryTest.kt | 3 + .../modules/grade/GradeAverageProviderTest.kt | 4 +- .../login/form/LoginFormPresenterTest.kt | 11 +- .../LoginStudentSelectPresenterTest.kt | 12 +- .../wulkanowy/utils/TimeExtensionTest.kt | 40 +- .../wulkanowy/utils/TimetableExtensionTest.kt | 51 +- 61 files changed, 2870 insertions(+), 400 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/46.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt diff --git a/app/build.gradle b/app/build.gradle index 135863562..4720ce4d7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -174,7 +174,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.4.4" + implementation "io.github.wulkanowy:sdk:42bce37748" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/46.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/46.json new file mode 100644 index 000000000..04518141c --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/46.json @@ -0,0 +1,2430 @@ +{ + "formatVersion": 1, + "database": { + "version": 46, + "identityHash": "f310243440ca00cbc35e62ebaca5c7d8", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f310243440ca00cbc35e62ebaca5c7d8')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index 6b4abb40d..2f4c74fbf 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -67,49 +67,7 @@ import io.github.wulkanowy.data.db.entities.Teacher import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.data.db.entities.TimetableHeader -import io.github.wulkanowy.data.db.migrations.Migration10 -import io.github.wulkanowy.data.db.migrations.Migration11 -import io.github.wulkanowy.data.db.migrations.Migration12 -import io.github.wulkanowy.data.db.migrations.Migration13 -import io.github.wulkanowy.data.db.migrations.Migration14 -import io.github.wulkanowy.data.db.migrations.Migration15 -import io.github.wulkanowy.data.db.migrations.Migration16 -import io.github.wulkanowy.data.db.migrations.Migration17 -import io.github.wulkanowy.data.db.migrations.Migration18 -import io.github.wulkanowy.data.db.migrations.Migration19 -import io.github.wulkanowy.data.db.migrations.Migration2 -import io.github.wulkanowy.data.db.migrations.Migration20 -import io.github.wulkanowy.data.db.migrations.Migration21 -import io.github.wulkanowy.data.db.migrations.Migration22 -import io.github.wulkanowy.data.db.migrations.Migration23 -import io.github.wulkanowy.data.db.migrations.Migration24 -import io.github.wulkanowy.data.db.migrations.Migration25 -import io.github.wulkanowy.data.db.migrations.Migration26 -import io.github.wulkanowy.data.db.migrations.Migration27 -import io.github.wulkanowy.data.db.migrations.Migration28 -import io.github.wulkanowy.data.db.migrations.Migration29 -import io.github.wulkanowy.data.db.migrations.Migration3 -import io.github.wulkanowy.data.db.migrations.Migration30 -import io.github.wulkanowy.data.db.migrations.Migration31 -import io.github.wulkanowy.data.db.migrations.Migration32 -import io.github.wulkanowy.data.db.migrations.Migration33 -import io.github.wulkanowy.data.db.migrations.Migration34 -import io.github.wulkanowy.data.db.migrations.Migration35 -import io.github.wulkanowy.data.db.migrations.Migration36 -import io.github.wulkanowy.data.db.migrations.Migration37 -import io.github.wulkanowy.data.db.migrations.Migration38 -import io.github.wulkanowy.data.db.migrations.Migration39 -import io.github.wulkanowy.data.db.migrations.Migration4 -import io.github.wulkanowy.data.db.migrations.Migration40 -import io.github.wulkanowy.data.db.migrations.Migration41 -import io.github.wulkanowy.data.db.migrations.Migration42 -import io.github.wulkanowy.data.db.migrations.Migration43 -import io.github.wulkanowy.data.db.migrations.Migration44 -import io.github.wulkanowy.data.db.migrations.Migration5 -import io.github.wulkanowy.data.db.migrations.Migration6 -import io.github.wulkanowy.data.db.migrations.Migration7 -import io.github.wulkanowy.data.db.migrations.Migration8 -import io.github.wulkanowy.data.db.migrations.Migration9 +import io.github.wulkanowy.data.db.migrations.* import io.github.wulkanowy.utils.AppInfo import javax.inject.Singleton @@ -157,7 +115,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 45 + const val VERSION_SCHEMA = 46 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), @@ -203,6 +161,7 @@ abstract class AppDatabase : RoomDatabase() { Migration42(), Migration43(), Migration44(), + Migration46(), ) fun newInstance( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt b/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt index 1993c4338..b7013a32a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt @@ -1,40 +1,33 @@ package io.github.wulkanowy.data.db import androidx.room.TypeConverter +import io.github.wulkanowy.utils.toTimestamp import kotlinx.serialization.SerializationException import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime import java.time.Month import java.time.ZoneOffset -import java.util.Date +import java.util.* class Converters { private val json = Json @TypeConverter - fun timestampToDate(value: Long?): LocalDate? = value?.run { - Date(value).toInstant().atZone(ZoneOffset.UTC).toLocalDate() - } + fun timestampToLocalDate(value: Long?): LocalDate? = + value?.let(::Date)?.toInstant()?.atZone(ZoneOffset.UTC)?.toLocalDate() @TypeConverter - fun dateToTimestamp(date: LocalDate?): Long? { - return date?.atStartOfDay()?.toInstant(ZoneOffset.UTC)?.toEpochMilli() - } + fun dateToTimestamp(date: LocalDate?): Long? = date?.toTimestamp() @TypeConverter - fun timestampToTime(value: Long?): LocalDateTime? = value?.let { - LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.UTC) - } + fun instantToTimestamp(instant: Instant?): Long? = instant?.toEpochMilli() @TypeConverter - fun timeToTimestamp(date: LocalDateTime?): Long? { - return date?.atZone(ZoneOffset.UTC)?.toInstant()?.toEpochMilli() - } + fun timestampToInstant(timestamp: Long?): Instant? = timestamp?.let(Instant::ofEpochMilli) @TypeConverter fun monthToInt(month: Month?) = month?.value diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt index e84bad592..ca9da9ea9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt @@ -4,7 +4,7 @@ import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Conference import kotlinx.coroutines.flow.Flow -import java.time.LocalDateTime +import java.time.Instant import javax.inject.Singleton @Dao @@ -12,5 +12,5 @@ import javax.inject.Singleton interface ConferenceDao : BaseDao { @Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :startDate") - fun loadAll(diaryId: Int, studentId: Int, startDate: LocalDateTime): Flow> + fun loadAll(diaryId: Int, studentId: Int, startDate: Instant): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt index 4ad946508..ba3958dbc 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt @@ -4,7 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.io.Serializable -import java.time.LocalDateTime +import java.time.Instant @Entity(tableName = "Conferences") data class Conference( @@ -27,7 +27,7 @@ data class Conference( @ColumnInfo(name = "conference_id") val conferenceId: Int, - val date: LocalDateTime + val date: Instant, ) : Serializable { @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt index fb7b60bbc..a42832ced 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import java.time.LocalDateTime +import java.time.Instant @Entity(tableName = "GradesSummary") data class GradeSummary( @@ -45,8 +45,8 @@ data class GradeSummary( var isFinalGradeNotified: Boolean = true @ColumnInfo(name = "predicted_grade_last_change") - var predictedGradeLastChange: LocalDateTime = LocalDateTime.now() + var predictedGradeLastChange: Instant = Instant.now() @ColumnInfo(name = "final_grade_last_change") - var finalGradeLastChange: LocalDateTime = LocalDateTime.now() + var finalGradeLastChange: Instant = Instant.now() } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt index 7b6e0dbf2..8782bc765 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt @@ -4,7 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.io.Serializable -import java.time.LocalDateTime +import java.time.Instant @Entity(tableName = "Messages") data class Message( @@ -29,7 +29,7 @@ data class Message( val subject: String, - val date: LocalDateTime, + val date: Instant, @ColumnInfo(name = "folder_id") val folderId: Int, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt index 83d82c0b9..887e43239 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt @@ -4,7 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.io.Serializable -import java.time.LocalDateTime +import java.time.Instant @Entity(tableName = "MobileDevices") data class MobileDevice( @@ -17,7 +17,7 @@ data class MobileDevice( val name: String, - val date: LocalDateTime + val date: Instant, ) : Serializable { @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Notification.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Notification.kt index 740137f0f..4867e3329 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Notification.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Notification.kt @@ -4,7 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import io.github.wulkanowy.services.sync.notifications.NotificationType -import java.time.LocalDateTime +import java.time.Instant @Entity(tableName = "Notifications") data class Notification( @@ -18,7 +18,7 @@ data class Notification( val type: NotificationType, - val date: LocalDateTime, + val date: Instant, val data: String? = null ) { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt index af9fe831a..76da9643d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt @@ -5,7 +5,7 @@ import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey import java.io.Serializable -import java.time.LocalDateTime +import java.time.Instant @Entity( tableName = "Students", @@ -74,7 +74,7 @@ data class Student( val isCurrent: Boolean, @ColumnInfo(name = "registration_date") - val registrationDate: LocalDateTime + val registrationDate: Instant, ) : Serializable { @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt index 29b3737bc..d23d388f9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt @@ -4,8 +4,8 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.io.Serializable +import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime @Entity(tableName = "Timetable") data class Timetable( @@ -18,9 +18,9 @@ data class Timetable( val number: Int, - val start: LocalDateTime, + val start: Instant, - val end: LocalDateTime, + val end: Instant, val date: LocalDate, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt index db32de874..478026102 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt @@ -4,9 +4,9 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.io.Serializable +import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime -import java.util.UUID +import java.util.* @Entity(tableName = "TimetableAdditional") data class TimetableAdditional( @@ -17,9 +17,9 @@ data class TimetableAdditional( @ColumnInfo(name = "diary_id") val diaryId: Int, - val start: LocalDateTime, + val start: Instant, - val end: LocalDateTime, + val end: Instant, val date: LocalDate, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt index 1dc38e14c..c827b82ba 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt @@ -43,12 +43,14 @@ class Migration12 : Migration(11, 12) { private fun getStudentsIds(database: SupportSQLiteDatabase): List { val students = mutableListOf() - val studentsCursor = database.query("SELECT student_id FROM Students") - if (studentsCursor.moveToFirst()) { - do { - students.add(studentsCursor.getInt(0)) - } while (studentsCursor.moveToNext()) + database.query("SELECT student_id FROM Students").use { + if (it.moveToFirst()) { + do { + students.add(it.getInt(0)) + } while (it.moveToNext()) + } } + return students } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt index 0cf8cd9b0..36de1e837 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt @@ -25,12 +25,14 @@ class Migration13 : Migration(12, 13) { private fun getStudentsIds(database: SupportSQLiteDatabase): MutableList> { val students = mutableListOf>() - val studentsCursor = database.query("SELECT id, school_name FROM Students") - if (studentsCursor.moveToFirst()) { - do { - students.add(studentsCursor.getInt(0) to studentsCursor.getString(1)) - } while (studentsCursor.moveToNext()) + database.query("SELECT id, school_name FROM Students").use { + if (it.moveToFirst()) { + do { + students.add(it.getInt(0) to it.getString(1)) + } while (it.moveToNext()) + } } + return students } @@ -42,12 +44,14 @@ class Migration13 : Migration(12, 13) { private fun getStudentsAndClassIds(database: SupportSQLiteDatabase): List> { val students = mutableListOf>() - val studentsCursor = database.query("SELECT student_id, class_id FROM Students") - if (studentsCursor.moveToFirst()) { - do { - students.add(studentsCursor.getInt(0) to studentsCursor.getInt(1)) - } while (studentsCursor.moveToNext()) + database.query("SELECT student_id, class_id FROM Students").use { + if (it.moveToFirst()) { + do { + students.add(it.getInt(0) to it.getInt(1)) + } while (it.moveToNext()) + } } + return students } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt index 6592228a1..5c60beead 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt @@ -22,24 +22,28 @@ class Migration27 : Migration(26, 27) { private fun getStudentsIdsAndNames(database: SupportSQLiteDatabase): MutableList> { val students = mutableListOf>() - val studentsCursor = database.query("SELECT id, user_login_id, student_name FROM Students") - if (studentsCursor.moveToFirst()) { - do { - students.add(Triple(studentsCursor.getLong(0), studentsCursor.getInt(1), studentsCursor.getString(2))) - } while (studentsCursor.moveToNext()) + database.query("SELECT id, user_login_id, student_name FROM Students").use { + if (it.moveToFirst()) { + do { + students.add(Triple(it.getLong(0), it.getInt(1), it.getString(2))) + } while (it.moveToNext()) + } } + return students } private fun getReportingUnits(database: SupportSQLiteDatabase): MutableList> { val units = mutableListOf>() - val unitsCursor = database.query("SELECT sender_id, sender_name FROM ReportingUnits") - if (unitsCursor.moveToFirst()) { - do { - units.add(unitsCursor.getInt(0) to unitsCursor.getString(1)) - } while (unitsCursor.moveToNext()) + database.query("SELECT sender_id, sender_name FROM ReportingUnits").use { + if (it.moveToFirst()) { + do { + units.add(it.getInt(0) to it.getString(1)) + } while (it.moveToNext()) + } } + return units } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt index cc540388c..f63431d00 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt @@ -10,15 +10,17 @@ class Migration35(private val appInfo: AppInfo) : Migration(34, 35) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0") - val studentsCursor = database.query("SELECT * FROM Students") - - while (studentsCursor.moveToNext()) { - val studentId = studentsCursor.getLongOrNull(0) - database.execSQL( - """UPDATE Students - SET avatar_color = ${appInfo.defaultColorsForAvatar.random()} - WHERE id = $studentId""" - ) + database.query("SELECT * FROM Students").use { + while (it.moveToNext()) { + val studentId = it.getLongOrNull(0) + database.execSQL( + """ + UPDATE Students + SET avatar_color = ${appInfo.defaultColorsForAvatar.random()} + WHERE id = $studentId + """ + ) + } } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt new file mode 100644 index 000000000..d3fa5cf93 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt @@ -0,0 +1,102 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import java.time.Instant +import java.time.ZoneId +import java.time.ZoneOffset + +class Migration46 : Migration(45, 46) { + + override fun migrate(database: SupportSQLiteDatabase) { + migrateConferences(database) + migrateMessages(database) + migrateMobileDevices(database) + migrateNotifications(database) + migrateTimetable(database) + migrateTimetableAdditional(database) + } + + private fun migrateConferences(database: SupportSQLiteDatabase) { + database.query("SELECT * FROM Conferences").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) + val timestampUtc = timestampLocal.timestampLocalToUTC() + + database.execSQL("UPDATE Conferences SET date = $timestampUtc WHERE id = $id") + } + } + } + + private fun migrateMessages(database: SupportSQLiteDatabase) { + database.query("SELECT * FROM Messages").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) + val timestampUtc = timestampLocal.timestampLocalToUTC() + + database.execSQL("UPDATE Messages SET date = $timestampUtc WHERE id = $id") + } + } + } + + private fun migrateMobileDevices(database: SupportSQLiteDatabase) { + database.query("SELECT * FROM MobileDevices").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) + val timestampUtc = timestampLocal.timestampLocalToUTC() + + database.execSQL("UPDATE MobileDevices SET date = $timestampUtc WHERE id = $id") + } + } + } + + private fun migrateNotifications(database: SupportSQLiteDatabase) { + database.query("SELECT * FROM Notifications").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) + val timestampUtc = timestampLocal.timestampLocalToUTC() + + database.execSQL("UPDATE Notifications SET date = $timestampUtc WHERE id = $id") + } + } + } + + private fun migrateTimetable(database: SupportSQLiteDatabase) { + database.query("SELECT * FROM Timetable").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start")) + val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end")) + val timestampUtcStart = timestampLocalStart.timestampLocalToUTC() + val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC() + + database.execSQL("UPDATE Timetable SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id") + } + } + } + + private fun migrateTimetableAdditional(database: SupportSQLiteDatabase) { + database.query("SELECT * FROM TimetableAdditional").use { + while (it.moveToNext()) { + val id = it.getLong(it.getColumnIndexOrThrow("id")) + val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start")) + val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end")) + val timestampUtcStart = timestampLocalStart.timestampLocalToUTC() + val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC() + + database.execSQL("UPDATE TimetableAdditional SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id") + } + } + } + + private fun Long.timestampLocalToUTC(): Long = Instant.ofEpochMilli(this) + .atZone(ZoneOffset.UTC) + .withZoneSameLocal(ZoneId.of("Europe/Warsaw")) + .withZoneSameInstant(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli() +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt index 52dc9b30b..17a9e5cdb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt @@ -10,7 +10,7 @@ fun List.mapToEntities(semester: Semester) = map { diaryId = semester.diaryId, agenda = it.agenda, conferenceId = it.id, - date = it.date, + date = it.dateZoned.toInstant(), presentOnConference = it.presentOnConference, subject = it.subject, title = it.title diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt index 913e4d030..13f0ab33e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -4,7 +4,7 @@ import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Student -import java.time.LocalDateTime +import java.time.Instant import io.github.wulkanowy.sdk.pojo.Message as SdkMessage import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient @@ -18,7 +18,7 @@ fun List.mapToEntities(student: Student) = map { senderId = it.sender?.loginId ?: 0, recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów", subject = it.subject.trim(), - date = it.date ?: LocalDateTime.now(), + date = it.dateZoned?.toInstant() ?: Instant.now(), folderId = it.folderId, unread = it.unread ?: false, removed = it.removed, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt index f0c375bfa..b1e96a27b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt @@ -3,13 +3,13 @@ package io.github.wulkanowy.data.mappers import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.pojos.MobileDeviceToken -import io.github.wulkanowy.sdk.pojo.Token as SdkToken import io.github.wulkanowy.sdk.pojo.Device as SdkDevice +import io.github.wulkanowy.sdk.pojo.Token as SdkToken fun List.mapToEntities(semester: Semester) = map { MobileDevice( userLoginId = semester.studentId, - date = it.createDate, + date = it.createDateZoned.toInstant(), deviceId = it.id, name = it.name ) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/StudentMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/StudentMapper.kt index c93323038..a2110d7f5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/StudentMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/StudentMapper.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.data.mappers import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import java.time.LocalDateTime +import java.time.Instant import io.github.wulkanowy.sdk.pojo.Student as SdkStudent fun List.mapToEntities(password: String = "", colors: List) = map { @@ -24,7 +24,7 @@ fun List.mapToEntities(password: String = "", colors: List) = scrapperBaseUrl = it.scrapperBaseUrl, loginType = it.loginType.name, isCurrent = false, - registrationDate = LocalDateTime.now(), + registrationDate = Instant.now(), mobileBaseUrl = it.mobileBaseUrl, privateKey = it.privateKey, certificateKey = it.certificateKey, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt index 045101c42..e55aa3cf7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt @@ -21,8 +21,8 @@ fun List.mapToEntities(semester: Semester) = map { studentId = semester.studentId, diaryId = semester.diaryId, number = it.number, - start = it.start, - end = it.end, + start = it.startZoned.toInstant(), + end = it.endZoned.toInstant(), date = it.date, subject = it.subject, subjectOld = it.subjectOld, @@ -45,8 +45,8 @@ fun List.mapToEntities(semester: Semester) = map { diaryId = semester.diaryId, subject = it.subject, date = it.date, - start = it.start, - end = it.end + start = it.startZoned.toInstant(), + end = it.endZoned.toInstant(), ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt index e32271833..a1667ccbc 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt @@ -6,16 +6,10 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import java.time.Instant -import java.time.LocalDateTime -import java.time.ZoneOffset import javax.inject.Inject import javax.inject.Singleton @@ -35,7 +29,7 @@ class ConferenceRepository @Inject constructor( semester: Semester, forceRefresh: Boolean, notify: Boolean = false, - startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC), + startDate: Instant = Instant.EPOCH, ) = networkBoundResource( mutex = saveFetchResultMutex, shouldFetch = { @@ -66,7 +60,7 @@ class ConferenceRepository @Inject constructor( conferenceDb.loadAll( diaryId = semester.diaryId, studentId = semester.studentId, - startDate = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC) + startDate = Instant.EPOCH, ) suspend fun updateConference(conference: List) = conferenceDb.updateAll(conference) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt index 6c574b48a..d539c14b9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -8,16 +8,12 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex -import java.time.LocalDateTime +import java.time.Instant import javax.inject.Inject import javax.inject.Singleton @@ -70,8 +66,8 @@ class GradeRepository @Inject constructor( newDetails: List, notify: Boolean ) { - val notifyBreakDate = oldGrades.maxByOrNull {it.date } - ?.date ?: student.registrationDate.toLocalDate() + val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date + ?: student.registrationDate.toLocalDate() gradeDb.deleteAll(oldGrades uniqueSubtract newDetails) gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach { if (it.date >= notifyBreakDate) it.apply { @@ -101,13 +97,13 @@ class GradeRepository @Inject constructor( } summary.predictedGradeLastChange = when { - oldSummary == null -> LocalDateTime.now() - summary.predictedGrade != oldSummary.predictedGrade -> LocalDateTime.now() + oldSummary == null -> Instant.now() + summary.predictedGrade != oldSummary.predictedGrade -> Instant.now() else -> oldSummary.predictedGradeLastChange } summary.finalGradeLastChange = when { - oldSummary == null -> LocalDateTime.now() - summary.finalGrade != oldSummary.finalGrade -> LocalDateTime.now() + oldSummary == null -> Instant.now() + summary.finalGrade != oldSummary.finalGrade -> Instant.now() else -> oldSummary.finalGradeLastChange } }) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt index c1738b36e..121324d8b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -6,11 +6,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt index e6437b167..4cd85586f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -7,24 +7,16 @@ import com.fredporciuncula.flow.preferences.FlowSharedPreferences import com.fredporciuncula.flow.preferences.Preference import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R -import io.github.wulkanowy.data.enums.AppTheme -import io.github.wulkanowy.data.enums.GradeColorTheme -import io.github.wulkanowy.data.enums.GradeExpandMode -import io.github.wulkanowy.data.enums.GradeSortingMode -import io.github.wulkanowy.data.enums.TimetableMode -import io.github.wulkanowy.sdk.toLocalDate +import io.github.wulkanowy.data.enums.* import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.grade.GradeAverageMode -import io.github.wulkanowy.utils.toLocalDateTime -import io.github.wulkanowy.utils.toTimestamp import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import java.time.LocalDate -import java.time.LocalDateTime +import java.time.Instant import javax.inject.Inject import javax.inject.Singleton @@ -208,10 +200,10 @@ class PreferencesRepository @Inject constructor( R.bool.pref_default_optional_arithmetic_average ) - var lasSyncDate: LocalDateTime + var lasSyncDate: Instant? get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date) - .toLocalDateTime() - set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply() + .takeIf { it != 0L }?.let(Instant::ofEpochMilli) + set(value) = sharedPref.edit().putLong("last_sync_date", value?.toEpochMilli() ?: 0).apply() var dashboardItemsPosition: Map? get() { @@ -270,11 +262,12 @@ class PreferencesRepository @Inject constructor( get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0) set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply() - var inAppReviewDate: LocalDate? + var inAppReviewDate: Instant? get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L } - ?.toLocalDate() - set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()) - .apply() + ?.let(Instant::ofEpochMilli) + set(value) = sharedPref.edit { + putLong(PREF_KEY_IN_APP_REVIEW_DATE, value?.toEpochMilli() ?: 0) + } var isAppReviewDone: Boolean get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false) diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt index b4786703a..c3ff1838e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -19,7 +19,6 @@ import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.getCompatColor -import io.github.wulkanowy.utils.toLocalDateTime import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn @@ -95,7 +94,7 @@ class TimetableNotificationReceiver : BroadcastReceiver() { val nextSubject = intent.getStringExtra(LESSON_NEXT_TITLE) val nextRoom = intent.getStringExtra(LESSON_NEXT_ROOM) - Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId") + Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: $start, student: $studentId") val notificationTitleResId = if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt index dc9b8f1da..42078d03f 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -28,12 +28,12 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.nickOrName -import io.github.wulkanowy.utils.toTimestamp import kotlinx.coroutines.withContext import timber.log.Timber +import java.time.Duration.ofMinutes +import java.time.Instant +import java.time.Instant.now import java.time.LocalDate -import java.time.LocalDateTime -import java.time.LocalDateTime.now import javax.inject.Inject class TimetableNotificationSchedulerHelper @Inject constructor( @@ -43,14 +43,14 @@ class TimetableNotificationSchedulerHelper @Inject constructor( private val dispatchersProvider: DispatchersProvider, ) { - private fun getRequestCode(time: LocalDateTime, studentId: Int) = - (time.toTimestamp() * studentId).toInt() + private fun getRequestCode(time: Instant, studentId: Int): Int = + (time.toEpochMilli() * studentId).toInt() private fun getUpcomingLessonTime( index: Int, day: List, lesson: Timetable - ) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) + ): Instant = day.getOrNull(index - 1)?.end ?: lesson.start.minus(ofMinutes(30)) suspend fun cancelScheduled(lessons: List, student: Student) { val studentId = student.studentId @@ -71,7 +71,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor( } } - private fun cancelScheduledTo(range: ClosedRange, requestCode: Int) { + private fun cancelScheduledTo(range: ClosedRange, requestCode: Int) { if (now() in range) cancelNotification() alarmManager.cancel( @@ -150,8 +150,8 @@ class TimetableNotificationSchedulerHelper @Inject constructor( putExtra(STUDENT_ID, student.studentId) putExtra(STUDENT_NAME, student.nickOrName) putExtra(LESSON_ROOM, lesson.room) - putExtra(LESSON_START, lesson.start.toTimestamp()) - putExtra(LESSON_END, lesson.end.toTimestamp()) + putExtra(LESSON_START, lesson.start.toEpochMilli()) + putExtra(LESSON_END, lesson.end.toEpochMilli()) putExtra(LESSON_TITLE, lesson.subject) putExtra(LESSON_NEXT_TITLE, nextLesson?.subject) putExtra(LESSON_NEXT_ROOM, nextLesson?.room) @@ -162,11 +162,11 @@ class TimetableNotificationSchedulerHelper @Inject constructor( intent: Intent, studentId: Int, notificationType: Int, - time: LocalDateTime + time: Instant ) { try { AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, RTC_WAKEUP, time.toTimestamp(), + alarmManager, RTC_WAKEUP, time.toEpochMilli(), PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { it.putExtra(LESSON_TYPE, notificationType) }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt index 4a9bfd58d..5dddd9a78 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt @@ -23,7 +23,7 @@ import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.getCompatColor import kotlinx.coroutines.withContext import timber.log.Timber -import java.time.LocalDateTime +import java.time.Instant import kotlin.random.Random @HiltWorker @@ -91,7 +91,7 @@ class SyncWorker @AssistedInject constructor( } errors.isNotEmpty() -> Result.retry() else -> { - preferencesRepository.lasSyncDate = LocalDateTime.now() + preferencesRepository.lasSyncDate = Instant.now() Result.success() } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt index 6fd6c1602..7ac532aeb 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt @@ -18,7 +18,7 @@ import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.getCompatBitmap import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.nickOrName -import java.time.LocalDateTime +import java.time.Instant import javax.inject.Inject import kotlin.random.Random @@ -169,7 +169,7 @@ class AppNotificationManager @Inject constructor( title = notificationData.title, content = notificationData.content, type = notificationType, - date = LocalDateTime.now() + date = Instant.now(), ) notificationRepository.saveNotification(notificationEntity) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt index 7bfef96a4..b1f9a7b06 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt @@ -11,8 +11,8 @@ import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.toFormattedString +import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime import javax.inject.Inject class ChangeTimetableNotification @Inject constructor( @@ -21,7 +21,7 @@ class ChangeTimetableNotification @Inject constructor( ) { suspend fun notify(items: List, student: Student) { - val currentTime = LocalDateTime.now() + val currentTime = Instant.now() val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime } val notificationDataList = changedLessons.groupBy { it.date } .map { (date, lessons) -> diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt index 8ef147886..d27c57285 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt @@ -11,7 +11,7 @@ import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.toFormattedString -import java.time.LocalDateTime +import java.time.Instant import javax.inject.Inject class NewConferenceNotification @Inject constructor( @@ -20,7 +20,7 @@ class NewConferenceNotification @Inject constructor( ) { suspend fun notify(items: List, student: Student) { - val today = LocalDateTime.now() + val today = Instant.now() val lines = items.filter { !it.date.isBefore(today) } .map { "${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt index 1bf5c7ad9..701656b55 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt @@ -13,13 +13,8 @@ import io.github.wulkanowy.ui.modules.about.license.LicenseFragment import io.github.wulkanowy.ui.modules.debug.DebugFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.getCompatDrawable -import io.github.wulkanowy.utils.openAppInMarket -import io.github.wulkanowy.utils.openEmailClient -import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.toFormattedString -import io.github.wulkanowy.utils.toLocalDateTime +import io.github.wulkanowy.utils.* +import java.time.Instant import javax.inject.Inject @AndroidEntryPoint @@ -38,7 +33,7 @@ class AboutFragment : BaseFragment(R.layout.fragment_about override val versionRes: Triple? get() = context?.run { val buildTimestamp = - appInfo.buildTimestamp.toLocalDateTime().toFormattedString("yyyy-MM-dd") + Instant.ofEpochMilli(appInfo.buildTimestamp).toFormattedString("yyyy-MM-dd") val versionSignature = "${appInfo.versionName}-${appInfo.buildFlavor} (${appInfo.versionCode}), $buildTimestamp" Triple( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt index 12be144cd..3b6dc7298 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt @@ -37,9 +37,7 @@ import io.github.wulkanowy.utils.left import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.toFormattedString import timber.log.Timber -import java.time.Duration -import java.time.LocalDate -import java.time.LocalDateTime +import java.time.* import java.util.Timer import javax.inject.Inject import kotlin.concurrent.timer @@ -291,7 +289,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter= 50 && - LocalDate.now().minusDays(14).isAfter(prefRepository.inAppReviewDate) + Instant.now().minus(Duration.ofDays(14)).isAfter(prefRepository.inAppReviewDate) ) { view?.showInAppReview() prefRepository.isAppReviewDone = true diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt index fc47e29ab..1ecb4a6ef 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt @@ -79,9 +79,7 @@ class SyncPresenter @Inject constructor( } private fun setSyncDateInView() { - val lastSyncDate = preferencesRepository.lasSyncDate - - if (lastSyncDate.year == 1970) return + val lastSyncDate = preferencesRepository.lasSyncDate ?: return view?.setLastSyncDate(lastSyncDate.toFormattedString("dd.MM.yyyy HH:mm:ss")) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt index 422cc50e8..eacd12c69 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt @@ -15,15 +15,10 @@ import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.enums.TimetableMode import io.github.wulkanowy.databinding.ItemTimetableBinding import io.github.wulkanowy.databinding.ItemTimetableSmallBinding -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.isJustFinished -import io.github.wulkanowy.utils.isShowTimeUntil -import io.github.wulkanowy.utils.left -import io.github.wulkanowy.utils.toFormattedString -import io.github.wulkanowy.utils.until +import io.github.wulkanowy.utils.* import timber.log.Timber -import java.time.LocalDateTime -import java.util.Timer +import java.time.Instant +import java.util.* import javax.inject.Inject import kotlin.concurrent.timer @@ -167,7 +162,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter i < position && !item.isStudentPlan }.size) ?.let { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt index 5bbaaa60f..c9243b12e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt @@ -16,7 +16,7 @@ import io.github.wulkanowy.utils.capitalise import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.toFormattedString -import java.time.LocalDateTime +import java.time.Instant class TimetableDialog : DialogFragment() { @@ -192,7 +192,7 @@ class TimetableDialog : DialogFragment() { } @SuppressLint("SetTextI18n") - private fun setTime(start: LocalDateTime, end: LocalDateTime) { + private fun setTime(start: Instant, end: Instant) { binding.timetableDialogTimeValue.text = "${start.toFormattedString("HH:mm")} - ${end.toFormattedString("HH:mm")}" } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt index 7bed5619e..c207165d3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt @@ -10,11 +10,9 @@ import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear import io.github.wulkanowy.utils.toLocalDate import kotlinx.coroutines.launch import timber.log.Timber -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.LocalTime +import java.time.* import java.time.temporal.ChronoUnit -import java.util.UUID +import java.util.* import javax.inject.Inject class AdditionalLessonAddPresenter @Inject constructor( @@ -138,8 +136,8 @@ class AdditionalLessonAddPresenter @Inject constructor( TimetableAdditional( studentId = semester.studentId, diaryId = semester.diaryId, - start = LocalDateTime.of(date, start), - end = LocalDateTime.of(date, end), + start = ZonedDateTime.of(date, start, ZoneId.systemDefault()).toInstant(), + end = ZonedDateTime.of(date, end, ZoneId.systemDefault()).toInstant(), date = date.plusWeeks(it), subject = subject ).apply { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index 411fa6625..18eefc5da 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -29,7 +29,6 @@ import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.runBlocking import timber.log.Timber import java.time.LocalDate -import java.time.ZoneOffset class TimetableWidgetFactory( private val timetableRepository: TimetableRepository, @@ -77,7 +76,7 @@ class TimetableWidgetFactory( if (date == LocalDate.now() && todayLastLessonEndTimestamp != null) { sharedPref.putLong( key = getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), - value = todayLastLessonEndTimestamp.toEpochSecond(ZoneOffset.UTC), + value = todayLastLessonEndTimestamp.epochSecond, sync = true ) } diff --git a/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt index 14b5989b0..09ccda899 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/MaterialDatePickerUtils.kt @@ -5,7 +5,6 @@ import com.google.android.material.datepicker.CalendarConstraints import com.google.android.material.datepicker.MaterialDatePicker import kotlinx.parcelize.Parcelize import java.time.LocalDate -import java.time.ZoneOffset import java.time.temporal.ChronoUnit fun Fragment.openMaterialDatePicker( @@ -16,17 +15,17 @@ fun Fragment.openMaterialDatePicker( ) { val constraintsBuilder = CalendarConstraints.Builder().apply { setValidator(CalendarDayRangeValidator(rangeStart, rangeEnd)) - setStart(rangeStart.toTimestamp(ZoneOffset.UTC)) - setEnd(rangeEnd.toTimestamp(ZoneOffset.UTC)) + setStart(rangeStart.toTimestamp()) + setEnd(rangeEnd.toTimestamp()) } val datePicker = MaterialDatePicker.Builder.datePicker() .setCalendarConstraints(constraintsBuilder.build()) - .setSelection(selected.toTimestamp(ZoneOffset.UTC)) + .setSelection(selected.toTimestamp()) .build() datePicker.addOnPositiveButtonClickListener { - val date = it.toLocalDateTime(ZoneOffset.UTC).toLocalDate() + val date = it.toLocalDateTime().toLocalDate() onDateSelected(date) } diff --git a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt index 6bf97bae7..c69fec65c 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt @@ -8,8 +8,9 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.enums.MessageFolder import timber.log.Timber +import java.time.Duration.ofMinutes +import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime import javax.inject.Inject fun getRefreshKey(name: String, semester: Semester, start: LocalDate, end: LocalDate): String { @@ -34,10 +35,10 @@ class AutoRefreshHelper @Inject constructor( ) { fun shouldBeRefreshed(key: String): Boolean { - val timestamp = sharedPref.getLong(key, 0).toLocalDateTime() + val timestamp = sharedPref.getLong(key, 0).let(Instant::ofEpochMilli) val servicesInterval = sharedPref.getString(context.getString(R.string.pref_key_services_interval), context.getString(R.string.pref_default_services_interval)).toLong() - val shouldBeRefreshed = timestamp < LocalDateTime.now().minusMinutes(servicesInterval) + val shouldBeRefreshed = timestamp < Instant.now().minus(ofMinutes(servicesInterval)) Timber.d("Check if $key need to be refreshed: $shouldBeRefreshed (last refresh: $timestamp, interval: $servicesInterval min)") @@ -45,6 +46,6 @@ class AutoRefreshHelper @Inject constructor( } fun updateLastRefreshTimestamp(key: String) { - sharedPref.putLong(key, LocalDateTime.now().toTimestamp()) + sharedPref.putLong(key, Instant.now().toEpochMilli()) } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt index da5fd3dbb..71d3fd173 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt @@ -12,12 +12,17 @@ import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException import okhttp3.internal.http2.StreamResetException import java.io.InterruptedIOException import java.net.ConnectException +import java.net.SocketException import java.net.SocketTimeoutException import java.net.UnknownHostException fun Resources.getString(error: Throwable) = when (error) { is UnknownHostException -> getString(R.string.error_no_internet) - is SocketTimeoutException, is InterruptedIOException, is ConnectException, is StreamResetException -> getString(R.string.error_timeout) + is SocketException, + is SocketTimeoutException, + is InterruptedIOException, + is ConnectException, + is StreamResetException -> getString(R.string.error_timeout) is NotLoggedInException -> getString(R.string.error_login_failed) is PasswordChangeRequiredException -> getString(R.string.error_password_change_required) is ServiceUnavailableException -> getString(R.string.error_service_unavailable) diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt index 355f3ab49..e7a50d0c3 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -1,40 +1,34 @@ package io.github.wulkanowy.utils import java.text.SimpleDateFormat -import java.time.DayOfWeek.FRIDAY -import java.time.DayOfWeek.MONDAY -import java.time.DayOfWeek.SATURDAY -import java.time.DayOfWeek.SUNDAY -import java.time.Instant -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.Month -import java.time.ZoneId -import java.time.ZoneOffset +import java.time.* +import java.time.DayOfWeek.* import java.time.format.DateTimeFormatter -import java.time.temporal.TemporalAdjusters.firstInMonth -import java.time.temporal.TemporalAdjusters.next -import java.time.temporal.TemporalAdjusters.previous -import java.util.Locale +import java.time.temporal.TemporalAdjusters.* +import java.util.* private const val DEFAULT_DATE_PATTERN = "dd.MM.yyyy" +fun LocalDate.toTimestamp(): Long = atStartOfDay() + .toInstant(ZoneOffset.UTC) + .toEpochMilli() + +fun Long.toLocalDateTime(): LocalDateTime = LocalDateTime.ofInstant( + Instant.ofEpochMilli(this), ZoneOffset.UTC +) + +fun Instant.toLocalDate(): LocalDate = atZone(ZoneOffset.UTC).toLocalDate() + fun String.toLocalDate(format: String = DEFAULT_DATE_PATTERN): LocalDate = LocalDate.parse(this, DateTimeFormatter.ofPattern(format)) -fun LocalDateTime.toTimestamp(tz: ZoneId = ZoneId.systemDefault()) = - atZone(tz).withZoneSameInstant(ZoneOffset.UTC).toInstant().toEpochMilli() - -fun Long.toLocalDateTime(tz: ZoneId = ZoneId.systemDefault()): LocalDateTime = - LocalDateTime.ofInstant(Instant.ofEpochMilli(this), tz) - -fun LocalDate.toTimestamp(tz: ZoneId = ZoneId.systemDefault()) = atStartOfDay().toTimestamp(tz) - fun LocalDate.toFormattedString(pattern: String = DEFAULT_DATE_PATTERN): String = format(DateTimeFormatter.ofPattern(pattern)) -fun LocalDateTime.toFormattedString(pattern: String = DEFAULT_DATE_PATTERN): String = - format(DateTimeFormatter.ofPattern(pattern)) +fun Instant.toFormattedString( + pattern: String = DEFAULT_DATE_PATTERN, + tz: ZoneId = ZoneId.systemDefault() +): String = atZone(tz).format(DateTimeFormatter.ofPattern(pattern)) fun Month.getFormattedName(): String { val formatter = SimpleDateFormat("LLLL", Locale.getDefault()) diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt index 9d15216c6..3e94463b9 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt @@ -3,10 +3,10 @@ package io.github.wulkanowy.utils import io.github.wulkanowy.data.db.entities.Timetable import java.time.Duration import java.time.Duration.between -import java.time.LocalDateTime -import java.time.LocalDateTime.now +import java.time.Instant +import java.time.Instant.now -fun Timetable.isShowTimeUntil(previousLessonEnd: LocalDateTime?) = when { +fun Timetable.isShowTimeUntil(previousLessonEnd: Instant?) = when { !isStudentPlan -> false canceled -> false now().isAfter(start) -> false diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index a0fa209fd..89ccade1b 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -4,7 +4,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.Sdk import java.time.LocalDate -import java.time.LocalDateTime.now +import java.time.Instant.now import io.github.wulkanowy.sdk.pojo.Semester as SdkSemester fun getSemesterEntity(diaryId: Int = 1, semesterId: Int = 1, start: LocalDate = LocalDate.now(), end: LocalDate = LocalDate.now(), semesterName: Int = 1) = Semester( diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt index 2350da459..261a6c1cf 100644 --- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt +++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt @@ -153,23 +153,25 @@ class Migration13Test : AbstractMigrationTest() { private fun getSemesters(db: SupportSQLiteDatabase, query: String): List> { val semesters = mutableListOf>() - val cursor = db.query(query) - if (cursor.moveToFirst()) { - do { - semesters.add(Semester( - studentId = cursor.getInt(1), - diaryId = cursor.getInt(2), - diaryName = cursor.getString(3), - semesterId = cursor.getInt(4), - semesterName = cursor.getInt(5), - classId = cursor.getInt(7), - unitId = cursor.getInt(8), - schoolYear = cursor.getInt(9), - start = Converters().timestampToDate(cursor.getLong(10))!!, - end = Converters().timestampToDate(cursor.getLong(11))!! - ) to (cursor.getInt(6) == 1)) - } while (cursor.moveToNext()) + db.query(query).use { + if (it.moveToFirst()) { + do { + semesters.add(Semester( + studentId = it.getInt(1), + diaryId = it.getInt(2), + diaryName = it.getString(3), + semesterId = it.getInt(4), + semesterName = it.getInt(5), + classId = it.getInt(7), + unitId = it.getInt(8), + schoolYear = it.getInt(9), + start = Converters().timestampToLocalDate(it.getLong(10))!!, + end = Converters().timestampToLocalDate(it.getLong(11))!! + ) to (it.getInt(6) == 1)) + } while (it.moveToNext()) + } } + return semesters.toList() } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt index 6dd30a579..f7968bc41 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt @@ -8,23 +8,17 @@ import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.toFirstResult -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK -import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue +import org.junit.Assert.* import org.junit.Before import org.junit.Test import java.time.LocalDate import java.time.LocalDate.of +import java.time.ZoneOffset import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade class GradeRepositoryTest { @@ -57,7 +51,11 @@ class GradeRepositoryTest { coEvery { gradeDb.deleteAll(any()) } just Runs coEvery { gradeDb.insertAll(any()) } returns listOf() - coEvery { gradeSummaryDb.loadAll(1, 1) } returnsMany listOf(flowOf(listOf()), flowOf(listOf()), flowOf(listOf())) + coEvery { gradeSummaryDb.loadAll(1, 1) } returnsMany listOf( + flowOf(listOf()), + flowOf(listOf()), + flowOf(listOf()) + ) coEvery { gradeSummaryDb.deleteAll(any()) } just Runs coEvery { gradeSummaryDb.insertAll(any()) } returns listOf() } @@ -65,7 +63,7 @@ class GradeRepositoryTest { @Test fun `mark grades older than registration date as read`() { // prepare - val boundaryDate = of(2019, 2, 27).atStartOfDay() + val boundaryDate = of(2019, 2, 27).atStartOfDay().toInstant(ZoneOffset.UTC) val remoteList = listOf( createGradeApi(5, 4.0, of(2019, 2, 25), "Ocena pojawiła się"), createGradeApi(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"), @@ -81,7 +79,13 @@ class GradeRepositoryTest { ) // execute - val res = runBlocking { gradeRepository.getGrades(student.copy(registrationDate = boundaryDate), semester, true).toFirstResult() } + val res = runBlocking { + gradeRepository.getGrades( + student = student.copy(registrationDate = boundaryDate), + semester = semester, + forceRefresh = true + ).toFirstResult() + } // verify assertEquals(null, res.error) @@ -101,9 +105,19 @@ class GradeRepositoryTest { fun `mitigate mark grades as unread when old grades changed`() { // prepare val remoteList = listOf( - createGradeApi(5, 2.0, of(2019, 2, 25), "Ocena ma datę, jest inna, ale nie zostanie powiadomiona"), + createGradeApi( + 5, + 2.0, + of(2019, 2, 25), + "Ocena ma datę, jest inna, ale nie zostanie powiadomiona" + ), createGradeApi(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"), - createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"), + createGradeApi( + 3, + 4.0, + of(2019, 2, 27), + "Ta jest z tego samego dnia co ostatnia lokalnie" + ), createGradeApi(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa") ) coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) @@ -111,7 +125,12 @@ class GradeRepositoryTest { val localList = listOf( createGradeApi(5, 3.0, of(2019, 2, 25), "Jedna ocena"), createGradeApi(4, 4.0, of(2019, 2, 26), "Druga"), - createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie") + createGradeApi( + 3, + 4.0, + of(2019, 2, 27), + "Ta jest z tego samego dnia co ostatnia lokalnie" + ) ) coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf( flowOf(localList.mapToEntities(semester)), @@ -248,18 +267,19 @@ class GradeRepositoryTest { assertEquals(0, res.data?.first?.size) } - private fun createGradeApi(value: Int, weight: Double, date: LocalDate, desc: String) = SdkGrade( - subject = "", - color = "", - comment = "", - date = date, - description = desc, - entry = "", - modifier = .0, - symbol = "", - teacher = "", - value = value.toDouble(), - weight = weight.toString(), - weightValue = weight - ) + private fun createGradeApi(value: Int, weight: Double, date: LocalDate, desc: String) = + SdkGrade( + subject = "", + color = "", + comment = "", + date = date, + description = desc, + entry = "", + modifier = .0, + symbol = "", + teacher = "", + value = value.toDouble(), + weight = weight.toString(), + weightValue = weight + ) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt index 052f08f00..f21fc1780 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -16,27 +16,25 @@ import io.github.wulkanowy.sdk.pojo.MessageDetails import io.github.wulkanowy.sdk.pojo.Sender import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.toFirstResult -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.checkEquals -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK -import io.mockk.just +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.Json import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import java.net.UnknownHostException -import java.time.LocalDateTime +import java.time.Instant +import java.time.ZoneOffset import kotlin.test.assertTrue +@OptIn(ExperimentalCoroutinesApi::class) class MessageRepositoryTest { @SpyK @@ -80,7 +78,7 @@ class MessageRepositoryTest { } @Test - fun `get messages when read by values was changed on already read message`() = runBlocking { + fun `get messages when read by values was changed on already read message`() = runTest { every { messageDb.loadAll(any(), any()) } returns flow { val dbMessage = getMessageEntity(3, "", false).apply { unreadBy = 10 @@ -239,7 +237,7 @@ class MessageRepositoryTest { senderId = 0, recipient = "Wielu adresatów", subject = "", - date = LocalDateTime.MAX, + date = Instant.EPOCH, folderId = 1, unread = unread, removed = false, @@ -261,7 +259,8 @@ class MessageRepositoryTest { recipients = listOf(), subject = "", content = content, - date = LocalDateTime.MAX, + date = Instant.EPOCH.atZone(ZoneOffset.UTC).toLocalDateTime(), + dateZoned = Instant.EPOCH.atZone(ZoneOffset.UTC), folderId = 1, unread = unread, unreadBy = 0, diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt index 52a076d3c..c5a7756ff 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt @@ -22,6 +22,7 @@ import org.junit.Assert import org.junit.Before import org.junit.Test import java.time.LocalDateTime.of +import java.time.ZoneId class MobileDeviceRepositoryTest { @@ -137,6 +138,8 @@ class MobileDeviceRepositoryTest { name = "", deviceId = "", createDate = of(2019, 5, day, 0, 0, 0), - modificationDate = of(2019, 5, day, 0, 0, 0) + modificationDate = of(2019, 5, day, 0, 0, 0), + createDateZoned = of(2019, 5, day, 0, 0, 0).atZone(ZoneId.systemDefault()), + modificationDateZoned = of(2019, 5, day, 0, 0, 0).atZone(ZoneId.systemDefault()) ) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt index edb3125eb..adb4f33a1 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt @@ -27,6 +27,7 @@ import org.junit.Test import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalDateTime.of +import java.time.ZoneId import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetable class TimetableRepositoryTest { @@ -107,6 +108,8 @@ class TimetableRepositoryTest { number = number, start = start, end = start.plusMinutes(45), + startZoned = start.atZone(ZoneId.systemDefault()), + endZoned = start.plusMinutes(45).atZone(ZoneId.systemDefault()), date = start.toLocalDate(), subject = subject, group = "", diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt index ec07e149f..f097cb845 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt @@ -23,9 +23,9 @@ import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test +import java.time.Instant import java.time.LocalDate.now import java.time.LocalDate.of -import java.time.LocalDateTime class GradeAverageProviderTest { @@ -63,7 +63,7 @@ class GradeAverageProviderTest { className = "", classId = 1, isCurrent = true, - registrationDate = LocalDateTime.now() + registrationDate = Instant.now() ) private val semesters = mutableListOf( diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt index 67d3e6266..9bcfb8b6c 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt @@ -6,18 +6,13 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.just -import io.mockk.verify import org.junit.Before import org.junit.Rule import org.junit.Test import java.io.IOException -import java.time.LocalDateTime.now +import java.time.Instant class LoginFormPresenterTest { @@ -121,7 +116,7 @@ class LoginFormPresenterTest { classId = 1, isCurrent = false, symbol = "", - registrationDate = now(), + registrationDate = Instant.now(), className = "", mobileBaseUrl = "", privateKey = "", diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt index 1ec885904..e52ec3ae2 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt @@ -7,18 +7,12 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.clearMocks -import io.mockk.coEvery -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.just -import io.mockk.verify import org.junit.Before import org.junit.Rule import org.junit.Test -import java.time.LocalDateTime.now +import java.time.Instant class LoginStudentSelectPresenterTest { @@ -55,7 +49,7 @@ class LoginStudentSelectPresenterTest { schoolSymbol = "", classId = 1, studentName = "", - registrationDate = now(), + registrationDate = Instant.now(), className = "", loginMode = "", certificateKey = "", diff --git a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt index 9a3bf9fea..70ad6d534 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt @@ -1,14 +1,13 @@ package io.github.wulkanowy.utils -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue +import org.junit.Assert.* import org.junit.Test +import java.time.Instant import java.time.LocalDate.of import java.time.LocalDateTime import java.time.Month.JANUARY import java.time.ZoneOffset -import java.util.Locale +import java.util.* class TimeExtensionTest { @@ -24,11 +23,15 @@ class TimeExtensionTest { } @Test - fun toFormattedStringLocalDateTimeTest() { - assertEquals("01.10.2018", LocalDateTime.of(2018, 10, 1, 10, 0, 0).toFormattedString()) + fun toFormattedStringFromInstantTest() { + assertEquals( + "01.10.2018", + LocalDateTime.of(2018, 10, 1, 10, 0, 0).toInstant(ZoneOffset.UTC).toFormattedString() + ) assertEquals( "2018-10-01 10:00:00", - LocalDateTime.of(2018, 10, 1, 10, 0, 0).toFormattedString("uuuu-MM-dd HH:mm:ss") + LocalDateTime.of(2018, 10, 1, 10, 0, 0).toInstant(ZoneOffset.UTC) + .toFormattedString("uuuu-MM-dd HH:mm:ss", ZoneOffset.UTC) ) } @@ -228,16 +231,23 @@ class TimeExtensionTest { } @Test - fun getLocalDateToTimestampUTC() { - assertEquals(0L, of(1970, 1, 1).toTimestamp(ZoneOffset.UTC)) - assertEquals(946684800000L, of(2000, 1, 1).toTimestamp(ZoneOffset.UTC)) - assertEquals(1640131200000L, of(2021, 12, 22).toTimestamp(ZoneOffset.UTC)) + fun getLocalDateToTimestamp() { + assertEquals(0L, of(1970, 1, 1).toTimestamp()) + assertEquals(946684800000L, of(2000, 1, 1).toTimestamp()) + assertEquals(1640131200000L, of(2021, 12, 22).toTimestamp()) } @Test - fun getLocalDateTimeToUtcTimestamp() { - assertEquals(0L, LocalDateTime.of(1970, 1, 1, 0, 0, 0).toTimestamp(ZoneOffset.UTC)) - assertEquals(946684800000L, LocalDateTime.of(2000, 1, 1, 0, 0, 0).toTimestamp(ZoneOffset.UTC)) - assertEquals(1640131200000L, LocalDateTime.of(2021, 12, 22, 0, 0, 0).toTimestamp(ZoneOffset.UTC)) + fun getLocalDateFromInstant() { + assertEquals(of(1970, 1, 1), Instant.ofEpochMilli(0).toLocalDate()) + assertEquals(of(2000, 1, 1), Instant.ofEpochMilli(946684800000).toLocalDate()) + assertEquals(of(2021, 12, 22), Instant.ofEpochMilli(1640131200000L).toLocalDate()) + } + + @Test + fun timestampToLocalDateTime() { + assertEquals(LocalDateTime.of(1970, 1, 1, 0, 0, 0), 0L.toLocalDateTime()) + assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0, 0), 946684800000.toLocalDateTime()) + assertEquals(LocalDateTime.of(2021, 12, 22, 0, 0, 0), 1640131200000L.toLocalDateTime()) } } diff --git a/app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt b/app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt index b7fa58c6f..a4d649e26 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt @@ -6,9 +6,8 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Test -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.LocalDateTime.now +import java.time.* +import java.time.Duration.ofMinutes class TimetableExtensionTest { @@ -17,52 +16,38 @@ class TimetableExtensionTest { assertFalse(getTimetableEntity().isShowTimeUntil(null)) assertFalse(getTimetableEntity(isStudentPlan = false).isShowTimeUntil(null)) assertFalse(getTimetableEntity(isStudentPlan = true, canceled = true).isShowTimeUntil(null)) - assertFalse(getTimetableEntity(isStudentPlan = true, canceled = false, start = now().minusSeconds(1)).isShowTimeUntil(null)) - assertFalse(getTimetableEntity(isStudentPlan = true, canceled = false, start = now().plusMinutes(5)).isShowTimeUntil(now().plusMinutes(5))) - assertFalse(getTimetableEntity(isStudentPlan = true, canceled = false, start = now().plusMinutes(61)).isShowTimeUntil(now().minusMinutes(5))) + assertFalse(getTimetableEntity(isStudentPlan = true, canceled = false, start = Instant.now().minusSeconds(1)).isShowTimeUntil(null)) + assertFalse(getTimetableEntity(isStudentPlan = true, canceled = false, start = Instant.now().plus(ofMinutes(5))).isShowTimeUntil(Instant.now().plus(ofMinutes(5)))) + assertFalse(getTimetableEntity(isStudentPlan = true, canceled = false, start = Instant.now().plus(ofMinutes(61))).isShowTimeUntil(Instant.now().minus(ofMinutes(5)))) - assertTrue(getTimetableEntity(isStudentPlan = true, canceled = false, start = now().plusMinutes(60)).isShowTimeUntil(now().minusMinutes(5))) - assertTrue(getTimetableEntity(isStudentPlan = true, canceled = false, start = now().plusMinutes(60)).isShowTimeUntil(null)) + assertTrue(getTimetableEntity(isStudentPlan = true, canceled = false, start = Instant.now().plus(ofMinutes(60))).isShowTimeUntil(Instant.now().minus(ofMinutes(5)))) + assertTrue(getTimetableEntity(isStudentPlan = true, canceled = false, start = Instant.now().plus(ofMinutes(60))).isShowTimeUntil(null)) - assertFalse(getTimetableEntity(isStudentPlan = true, canceled = false, start = now().minusSeconds(1)).isShowTimeUntil(null)) + assertFalse(getTimetableEntity(isStudentPlan = true, canceled = false, start = Instant.now().minusSeconds(1)).isShowTimeUntil(null)) } @Test fun getLeft() { assertEquals(null, getTimetableEntity(canceled = true).left) - assertEquals(null, getTimetableEntity(start = now().plusMinutes(5), end = now().plusMinutes(50)).left) - assertEquals(null, getTimetableEntity(start = now().minusMinutes(1), end = now().plusMinutes(44), isStudentPlan = false).left) - assertNotEquals( - null, - getTimetableEntity( - start = now().minusMinutes(1), - end = now().plusMinutes(44), - isStudentPlan = true - ).left - ) - assertNotEquals( - null, - getTimetableEntity( - start = now(), - end = now().plusMinutes(45), - isStudentPlan = true - ).left - ) + assertEquals(null, getTimetableEntity(start = Instant.now().plus(ofMinutes(5)), end = Instant.now().plus(ofMinutes(50))).left) + assertEquals(null, getTimetableEntity(start = Instant.now().minus(ofMinutes(1)), end = Instant.now().plus(ofMinutes(44)), isStudentPlan = false).left) + assertNotEquals(null, getTimetableEntity(start = Instant.now().minus(ofMinutes(1)), end = Instant.now().plus(ofMinutes(44)), isStudentPlan = true).left) + assertNotEquals(null, getTimetableEntity(start = Instant.now(), end = Instant.now().plus(ofMinutes(45)), isStudentPlan = true).left) } @Test fun isJustFinished() { - assertFalse(getTimetableEntity(end = now().minusSeconds(16)).isJustFinished) - assertTrue(getTimetableEntity(end = now().minusSeconds(14)).isJustFinished) - assertTrue(getTimetableEntity(end = now().minusSeconds(1)).isJustFinished) - assertFalse(getTimetableEntity(end = now().plusSeconds(1)).isJustFinished) + assertFalse(getTimetableEntity(end = Instant.now().minusSeconds(16)).isJustFinished) + assertTrue(getTimetableEntity(end = Instant.now().minusSeconds(14)).isJustFinished) + assertTrue(getTimetableEntity(end = Instant.now().minusSeconds(1)).isJustFinished) + assertFalse(getTimetableEntity(end = Instant.now().plusSeconds(1)).isJustFinished) } private fun getTimetableEntity( isStudentPlan: Boolean = false, canceled: Boolean = false, - start: LocalDateTime = now(), - end: LocalDateTime = now() + start: Instant = Instant.now(), + end: Instant = Instant.now() ) = Timetable( studentId = 0, subject = "", From 5321d00ee92b102572b90df06a46055417e110af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 31 Dec 2021 12:10:56 +0100 Subject: [PATCH 23/30] Fix play flavor build (#1740) --- .../wulkanowy/services/messaging/AppMessagingService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/play/java/io/github/wulkanowy/services/messaging/AppMessagingService.kt b/app/src/play/java/io/github/wulkanowy/services/messaging/AppMessagingService.kt index 6e5c7e89c..d9a3780ba 100644 --- a/app/src/play/java/io/github/wulkanowy/services/messaging/AppMessagingService.kt +++ b/app/src/play/java/io/github/wulkanowy/services/messaging/AppMessagingService.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import timber.log.Timber -import java.time.LocalDateTime +import java.time.Instant import javax.inject.Inject @SuppressLint("MissingFirebaseInstanceTokenRefresh") @@ -36,7 +36,7 @@ class AppMessagingService : FirebaseMessagingService() { title = title, content = content, data = customData, - date = LocalDateTime.now(), + date = Instant.now(), type = NotificationType.PUSH, studentId = -1 ) From 20673c4eaddefdd3612314a52ff5376544d4b796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 31 Dec 2021 12:21:52 +0100 Subject: [PATCH 24/30] Add basic support for kindergarten students (#1738) --- app/build.gradle | 2 +- .../47.json | 2438 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 5 +- .../wulkanowy/data/db/entities/Semester.kt | 12 +- .../wulkanowy/data/mappers/SemesterMapper.kt | 1 + .../data/repositories/AttendanceRepository.kt | 14 +- .../AttendanceSummaryRepository.kt | 9 +- .../CompletedLessonsRepository.kt | 11 +- .../data/repositories/ConferenceRepository.kt | 3 +- .../data/repositories/ExamRepository.kt | 11 +- .../data/repositories/GradeRepository.kt | 2 +- .../repositories/GradeStatisticsRepository.kt | 17 +- .../data/repositories/HomeworkRepository.kt | 11 +- .../repositories/MobileDeviceRepository.kt | 15 +- .../data/repositories/NoteRepository.kt | 3 +- .../data/repositories/SchoolRepository.kt | 4 +- .../data/repositories/SemesterRepository.kt | 18 +- .../repositories/StudentInfoRepository.kt | 3 +- .../data/repositories/SubjectRepository.kt | 9 +- .../data/repositories/TeacherRepository.kt | 9 +- .../data/repositories/TimetableRepository.kt | 16 +- .../io/github/wulkanowy/TestEnityCreator.kt | 2 + .../data/db/migrations/Migration13Test.kt | 1 + 23 files changed, 2516 insertions(+), 100 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/47.json diff --git a/app/build.gradle b/app/build.gradle index 4720ce4d7..b7a075b98 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -174,7 +174,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:42bce37748" + implementation "io.github.wulkanowy:sdk:f6f32b755a" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/47.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/47.json new file mode 100644 index 000000000..3f8291eac --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/47.json @@ -0,0 +1,2438 @@ +{ + "formatVersion": 1, + "database": { + "version": 47, + "identityHash": "ac88c80d4bb923b22f22ce4f91521306", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `is_dismissible` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ac88c80d4bb923b22f22ce4f91521306')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index 2f4c74fbf..24bb3917e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -106,7 +106,8 @@ import javax.inject.Singleton AdminMessage::class ], autoMigrations = [ - AutoMigration(from = 44, to = 45) + AutoMigration(from = 44, to = 45), + AutoMigration(from = 46, to = 47), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -115,7 +116,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 46 + const val VERSION_SCHEMA = 47 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt index 3dd7ee0cb..187890c9b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt @@ -7,7 +7,12 @@ import androidx.room.PrimaryKey import java.io.Serializable import java.time.LocalDate -@Entity(tableName = "Semesters", indices = [Index(value = ["student_id", "diary_id", "semester_id"], unique = true)]) +@Entity( + tableName = "Semesters", indices = [Index( + value = ["student_id", "diary_id", "kindergarten_diary_id", "semester_id"], + unique = true + )] +) data class Semester( @ColumnInfo(name = "student_id") @@ -16,6 +21,9 @@ data class Semester( @ColumnInfo(name = "diary_id") val diaryId: Int, + @ColumnInfo(name = "kindergarten_diary_id", defaultValue = "0") + val kindergartenDiaryId: Int, + @ColumnInfo(name = "diary_name") val diaryName: String, @@ -37,7 +45,7 @@ data class Semester( @ColumnInfo(name = "unit_id") val unitId: Int -): Serializable { +) : Serializable { @PrimaryKey(autoGenerate = true) var id: Long = 0 diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/SemesterMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/SemesterMapper.kt index acd93a91a..67d68a1e3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/SemesterMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/SemesterMapper.kt @@ -7,6 +7,7 @@ fun List.mapToEntities(studentId: Int) = map { Semester( studentId = studentId, diaryId = it.diaryId, + kindergartenDiaryId = it.kindergartenDiaryId, diaryName = it.diaryName, schoolYear = it.schoolYear, semesterId = it.semesterId, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index ec9198175..7184f5576 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -7,13 +7,7 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Absent -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import java.time.LocalDate @@ -52,7 +46,8 @@ class AttendanceRepository @Inject constructor( attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getAttendance(start.monday, end.sunday, semester.semesterId) .mapToEntities(semester) }, @@ -90,7 +85,8 @@ class AttendanceRepository @Inject constructor( timeId = attendance.timeId ) } - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .excuseForAbsence(items, reason) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt index bc1fb2343..0857475f8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -5,11 +5,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -38,7 +34,8 @@ class AttendanceSummaryRepository @Inject constructor( }, query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getAttendanceSummary(subjectId) .mapToEntities(semester, subjectId) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt index c2e5a7217..2055f3f47 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt @@ -5,13 +5,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex import java.time.LocalDate import javax.inject.Inject @@ -51,7 +45,8 @@ class CompletedLessonsRepository @Inject constructor( ) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getCompletedLessons(start.monday, end.sunday) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt index a1667ccbc..6af24d73f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt @@ -40,7 +40,8 @@ class ConferenceRepository @Inject constructor( conferenceDb.loadAll(semester.diaryId, student.studentId, startDate) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getConferences() .mapToEntities(semester) .filter { it.date >= startDate } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index 9bdac0658..c655c8001 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -6,13 +6,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.endExamsDay -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.startExamsDay -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import java.time.LocalDate @@ -54,7 +48,8 @@ class ExamRepository @Inject constructor( ) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getExams(start.startExamsDay, start.endExamsDay, semester.semesterId) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt index d539c14b9..f4087a887 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -47,7 +47,7 @@ class GradeRepository @Inject constructor( }, fetch = { val (details, summary) = sdk.init(student) - .switchDiary(semester.diaryId, semester.schoolYear) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getGrades(semester.semesterId) details.mapToEntities(semester) to summary.mapToEntities(semester) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt index 6c36f163b..356c203d0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt @@ -12,13 +12,9 @@ import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex -import java.util.Locale +import java.util.* import javax.inject.Inject import javax.inject.Singleton @@ -54,7 +50,8 @@ class GradeStatisticsRepository @Inject constructor( }, query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getGradesPartialStatistics(semester.semesterId) .mapToEntities(semester) }, @@ -101,7 +98,8 @@ class GradeStatisticsRepository @Inject constructor( }, query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getGradesSemesterStatistics(semester.semesterId) .mapToEntities(semester) }, @@ -155,7 +153,8 @@ class GradeStatisticsRepository @Inject constructor( }, query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getGradesPointsStatistics(semester.semesterId) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt index 95a375a45..900d9a68e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -6,13 +6,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex import java.time.LocalDate import javax.inject.Inject @@ -53,7 +47,8 @@ class HomeworkRepository @Inject constructor( ) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getHomework(start.monday, end.sunday) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt index bf17cbbc5..f825c36df 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -8,11 +8,7 @@ import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -40,7 +36,8 @@ class MobileDeviceRepository @Inject constructor( }, query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getRegisteredDevices() .mapToEntities(semester) }, @@ -53,14 +50,16 @@ class MobileDeviceRepository @Inject constructor( ) suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .unregisterDevice(device.deviceId) mobileDb.deleteAll(listOf(device)) } suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + return sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getToken() .mapToMobileDeviceToken() } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt index 121324d8b..19ad8f037 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -38,7 +38,8 @@ class NoteRepository @Inject constructor( }, query = { noteDb.loadAll(student.studentId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getNotes(semester.semesterId) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt index 288a1fb67..880a6a74c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt @@ -38,7 +38,9 @@ class SchoolRepository @Inject constructor( }, query = { schoolDb.load(semester.studentId, semester.classId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool() + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .getSchool() .mapToEntity(semester) }, saveFetchResult = { old, new -> diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt index cc954558f..96f019223 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt @@ -5,11 +5,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.DispatchersProvider -import io.github.wulkanowy.utils.getCurrentOrLast -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.isCurrent -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject @@ -43,10 +39,14 @@ class SemesterRepository @Inject constructor( ): Boolean { val isNoSemesters = semesters.isEmpty() - val isRefreshOnModeChangeRequired = - if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { - semesters.firstOrNull { it.isCurrent }?.diaryId == 0 - } else false + val isRefreshOnModeChangeRequired = when { + Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> { + semesters.firstOrNull { it.isCurrent }?.let { + 0 == it.diaryId && 0 == it.kindergartenDiaryId + } == true + } + else -> false + } val isRefreshOnNoCurrentAppropriate = refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt index e98daedf2..1fa91dd46 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt @@ -28,7 +28,8 @@ class StudentInfoRepository @Inject constructor( shouldFetch = { it == null || forceRefresh }, query = { studentInfoDao.loadStudentInfo(student.studentId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getStudentInfo().mapToEntity(semester) }, saveFetchResult = { old, new -> diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt index d81cb7c92..b9bca028f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt @@ -5,11 +5,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -37,7 +33,8 @@ class SubjectRepository @Inject constructor( }, query = { subjectDao.loadAll(semester.diaryId, semester.studentId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getSubjects().mapToEntities(semester) }, saveFetchResult = { old, new -> diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt index 029b2707a..6b615c7a7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt @@ -5,11 +5,7 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -37,7 +33,8 @@ class TeacherRepository @Inject constructor( }, query = { teacherDb.loadAll(semester.studentId, semester.classId) }, fetch = { - sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getTeachers(semester.semesterId) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 1f7bb1cff..7534640c3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -3,22 +3,12 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableHeaderDao -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.db.entities.TimetableAdditional -import io.github.wulkanowy.data.db.entities.TimetableHeader +import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.uniqueSubtract +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.sync.Mutex @@ -62,7 +52,7 @@ class TimetableRepository @Inject constructor( query = { getFullTimetableFromDatabase(student, semester, start, end) }, fetch = { val timetableFull = sdk.init(student) - .switchDiary(semester.diaryId, semester.schoolYear) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getTimetableFull(start.monday, end.sunday) timetableFull.mapToEntities(semester) diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index 89ccade1b..225399306 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -10,6 +10,7 @@ import io.github.wulkanowy.sdk.pojo.Semester as SdkSemester fun getSemesterEntity(diaryId: Int = 1, semesterId: Int = 1, start: LocalDate = LocalDate.now(), end: LocalDate = LocalDate.now(), semesterName: Int = 1) = Semester( studentId = 1, diaryId = diaryId, + kindergartenDiaryId = 0, semesterId = semesterId, diaryName = "$semesterId", schoolYear = 1970, @@ -22,6 +23,7 @@ fun getSemesterEntity(diaryId: Int = 1, semesterId: Int = 1, start: LocalDate = fun getSemesterPojo(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalDate, semesterName: Int = 1) = SdkSemester( diaryId = diaryId, + kindergartenDiaryId = 0, semesterId = semesterId, diaryName = "$semesterId", schoolYear = 1970, diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt index 261a6c1cf..bdfb4137d 100644 --- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt +++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt @@ -159,6 +159,7 @@ class Migration13Test : AbstractMigrationTest() { semesters.add(Semester( studentId = it.getInt(1), diaryId = it.getInt(2), + kindergartenDiaryId = 0, diaryName = it.getString(3), semesterId = it.getInt(4), semesterName = it.getInt(5), From a03bcf8e62a06295d22216e473a8247269b36fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 31 Dec 2021 12:36:14 +0100 Subject: [PATCH 25/30] Display comment after entry in grade notifications (#1741) --- .../services/sync/notifications/NewGradeNotification.kt | 5 ++++- .../ui/modules/debug/notification/mock/gradeDetails.kt | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt index 39ecbe33d..32d2ba6a0 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt @@ -22,7 +22,10 @@ class NewGradeNotification @Inject constructor( val notificationDataList = items.map { NotificationData( title = context.getPlural(R.plurals.grade_new_items, 1), - content = "${it.subject}: ${it.entry}", + content = buildString { + append("${it.subject}: ${it.entry}") + if (it.comment.isNotBlank()) append(" (${it.comment})") + }, intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDetails.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDetails.kt index f9c481e39..77b60188b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDetails.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDetails.kt @@ -5,7 +5,7 @@ import java.time.LocalDate val debugGradeDetailsItems = listOf( generateGrade("Matematyka", "+"), - generateGrade("Matematyka", "2="), + generateGrade("Matematyka", "120", comment = "%"), generateGrade("Fizyka", "-"), generateGrade("Geografia", "4+"), generateGrade("Sieci komputerowe", "1"), @@ -17,14 +17,14 @@ val debugGradeDetailsItems = listOf( generateGrade("Wychowanie fizyczne", "5"), ) -private fun generateGrade(subject: String, entry: String) = Grade( +private fun generateGrade(subject: String, entry: String, comment: String = "") = Grade( subject = subject, entry = entry, semesterId = 0, studentId = 0, value = 0.0, modifier = 0.0, - comment = "", + comment = comment, color = "", gradeSymbol = "", description = "", From bc672e94f848597823e2d1b4dbdd2a002868d08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 1 Jan 2022 13:48:58 +0100 Subject: [PATCH 26/30] Strip html from school announcements notifications (#1743) --- app/build.gradle | 2 +- .../sync/notifications/NewSchoolAnnouncementNotification.kt | 3 ++- .../ui/modules/debug/notification/mock/schoolAnnouncement.kt | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b7a075b98..840229297 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -174,7 +174,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:f6f32b755a" + implementation "io.github.wulkanowy:sdk:6e1b8eb26a" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt index 6b839d298..695438a70 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.services.sync.notifications import android.content.Context +import androidx.core.text.parseAsHtml import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.SchoolAnnouncement @@ -28,7 +29,7 @@ class NewSchoolAnnouncementNotification @Inject constructor( R.plurals.school_announcement_notify_new_item_title, 1 ), - content = "${it.subject}: ${it.content}" + content = "${it.subject}: ${it.content.parseAsHtml()}" ) } val groupNotificationData = GroupNotificationData( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt index 42524e6e0..9b21f08e6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt @@ -4,13 +4,13 @@ import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import java.time.LocalDate val debugSchoolAnnouncementItems = listOf( - generateAnnouncement("Dzień wolny od zajęć dydaktycznych", "Dzień wolny od zajęć dydaktycznych\n03.05.2021 - poniedziałek"), + generateAnnouncement("Dzień wolny od zajęć dydaktycznych", "Dzień wolny od zajęć dydaktycznych
03.05.2021 – poniedziałek"), generateAnnouncement("Zasady bezpieczeństwa", "Wszyscy uczniowie są zobowiązani do noszenia maseczek"), generateAnnouncement("Święto szkoły", "W najbliższych dniach obchodzimy święto szkoły, podczas którego..."), generateAnnouncement("Rocznica odzyskania przez szkołę sztandaru", "Juz niedługo, bo za tydzień, a dokładnie za 8 dni..."), generateAnnouncement("Ogłoszenie w sprawie otwarcia stołówki", "Wszyscy uczniowie zainteresowani obiadami w szkole..."), generateAnnouncement("Uczniowie proszeni do sekretariatu", "Kuba i Jacek z klasy czwartej proszeni do dyrektora w trybie pilnym"), - generateAnnouncement("Dzień wolny od zajęć dydaktycznych", "Dzień wolny od zajęć dydaktycznych\n21.06.2021 - poniedziałek"), + generateAnnouncement("Dzień wolny od zajęć dydaktycznych", "Dzień wolny od zajęć dydaktycznych
21.06.2021 – poniedziałek"), generateAnnouncement("Zasady bezpieczeństwa", "Wszyscy uczniowie są zobowiązani do zdjęcia maseczek"), generateAnnouncement("Święto państwowe", "W najbliższych dniach obchodzimy święto państwowe, podczas którego..."), generateAnnouncement("Uczniowie proszeni do sekretariatu", "Kuba i Jacek z klasy czwartej proszeni do dyrektora w trybie wolnym"), From 88773223575820556238078c76a1129263550433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 1 Jan 2022 13:52:51 +0100 Subject: [PATCH 27/30] Differentiate school announcements by userLoginId (#1744) --- .../github/wulkanowy/data/mappers/DirectorInformationMapper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt index e6bf000b1..d059db816 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformatio fun List.mapToEntities(student: Student) = map { SchoolAnnouncement( - studentId = student.studentId, + studentId = student.userLoginId, date = it.date, subject = it.subject, content = it.content, From aff1a7030d2d6203a3ae86b14e832660dec14fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 1 Jan 2022 15:46:08 +0100 Subject: [PATCH 28/30] Add a custom error message for ssl errors due to invalid clock setting (#1742) --- .../github/wulkanowy/ui/base/ErrorDialog.kt | 24 +----- .../github/wulkanowy/ui/base/ErrorHandler.kt | 4 +- .../ui/modules/dashboard/DashboardFragment.kt | 9 +-- .../modules/dashboard/DashboardPresenter.kt | 1 + .../ui/modules/dashboard/DashboardView.kt | 2 +- .../wulkanowy/utils/ExceptionExtension.kt | 74 +++++++++++++++++++ .../wulkanowy/utils/ResourcesExtension.kt | 34 --------- app/src/main/res/values/strings.xml | 1 + 8 files changed, 84 insertions(+), 65 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt index c2ffff1f5..48c003b7e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt @@ -16,15 +16,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogErrorBinding -import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException -import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException -import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException import io.github.wulkanowy.utils.* -import okhttp3.internal.http2.StreamResetException -import java.io.InterruptedIOException -import java.net.ConnectException -import java.net.SocketTimeoutException -import java.net.UnknownHostException import javax.inject.Inject @AndroidEntryPoint @@ -67,7 +59,7 @@ class ErrorDialog : DialogFragment() { private fun DialogErrorBinding.bindErrorDetails(error: Throwable) { return with(this) { - errorDialogHumanizedMessage.text = resources.getString(error) + errorDialogHumanizedMessage.text = resources.getErrorString(error) errorDialogErrorMessage.text = error.localizedMessage errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank() errorDialogContent.text = error.stackTraceToString() @@ -77,22 +69,10 @@ class ErrorDialog : DialogFragment() { private fun AlertDialog.enableReportButtonIfErrorIsReportable(error: Throwable) { setOnShowListener { - getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = isErrorShouldBeReported(error) + getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = error.isShouldBeReported() } } - private fun isErrorShouldBeReported(error: Throwable): Boolean = when (error) { - is UnknownHostException, - is InterruptedIOException, - is ConnectException, - is StreamResetException, - is SocketTimeoutException, - is ServiceUnavailableException, - is FeatureDisabledException, - is FeatureNotAvailableException -> false - else -> true - } - private fun copyErrorToClipboard(errorStacktrace: String) { val clip = ClipData.newPlainText("Error details", errorStacktrace) requireActivity().getSystemService()?.setPrimaryClip(clip) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index fbc994e2c..afe200e9a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -5,7 +5,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException -import io.github.wulkanowy.utils.getString +import io.github.wulkanowy.utils.getErrorString import io.github.wulkanowy.utils.security.ScramblerException import timber.log.Timber import javax.inject.Inject @@ -26,7 +26,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co } protected open fun proceed(error: Throwable) { - showErrorMessage(context.resources.getString(error), error) + showErrorMessage(context.resources.getErrorString(error), error) when (error) { is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl) is ScramblerException, is BadCredentialsException -> onSessionExpired() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt index 12a28ea73..88c281ecd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -28,10 +28,7 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import java.time.LocalDate import javax.inject.Inject @@ -178,8 +175,8 @@ class DashboardFragment : BaseFragment(R.layout.fragme binding.dashboardErrorContainer.isVisible = show } - override fun setErrorDetails(message: String) { - binding.dashboardErrorMessage.text = message + override fun setErrorDetails(error: Throwable) { + binding.dashboardErrorMessage.text = requireContext().resources.getErrorString(error) } override fun resetView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt index 360d6be84..a1845ab59 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt @@ -714,6 +714,7 @@ class DashboardPresenter @Inject constructor( if ((forceRefresh && wasGeneralError) || !forceRefresh) { showContent(false) showErrorView(true) + setErrorDetails(lastError) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt index 730e19a35..2cc2f1d2d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt @@ -18,7 +18,7 @@ interface DashboardView : BaseView { fun showErrorView(show: Boolean) - fun setErrorDetails(message: String) + fun setErrorDetails(error: Throwable) fun resetView() diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt new file mode 100644 index 000000000..43cecd400 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt @@ -0,0 +1,74 @@ +package io.github.wulkanowy.utils + +import android.content.res.Resources +import io.github.wulkanowy.R +import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException +import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException +import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException +import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException +import io.github.wulkanowy.sdk.scrapper.exception.VulcanException +import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException +import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException +import okhttp3.internal.http2.StreamResetException +import java.io.InterruptedIOException +import java.net.ConnectException +import java.net.SocketException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import java.security.cert.CertificateExpiredException +import java.security.cert.CertificateNotYetValidException +import javax.net.ssl.SSLHandshakeException + +fun Resources.getErrorString(error: Throwable): String = when (error) { + is UnknownHostException -> R.string.error_no_internet + is SocketException, + is SocketTimeoutException, + is InterruptedIOException, + is ConnectException, + is StreamResetException -> R.string.error_timeout + is NotLoggedInException -> R.string.error_login_failed + is PasswordChangeRequiredException -> R.string.error_password_change_required + is ServiceUnavailableException -> R.string.error_service_unavailable + is FeatureDisabledException -> R.string.error_feature_disabled + is FeatureNotAvailableException -> R.string.error_feature_not_available + is VulcanException -> R.string.error_unknown_uonet + is ScrapperException -> R.string.error_unknown_app + is SSLHandshakeException -> when { + error.isCausedByCertificateNotValidNow() -> R.string.error_invalid_device_datetime + else -> R.string.error_timeout + } + else -> R.string.error_unknown +}.let { getString(it) } + +fun Throwable.isShouldBeReported(): Boolean = when (this) { + is UnknownHostException, + is SocketException, + is SocketTimeoutException, + is InterruptedIOException, + is ConnectException, + is StreamResetException, + is ServiceUnavailableException, + is FeatureDisabledException, + is FeatureNotAvailableException -> false + is SSLHandshakeException -> when { + isCausedByCertificateNotValidNow() -> false + else -> true + } + else -> true +} + +private fun Throwable?.isCausedByCertificateNotValidNow(): Boolean { + var exception = this + do { + if (exception.isCertificateNotValidNow()) return true + + exception = exception?.cause + } while (exception != null) + return false +} + +private fun Throwable?.isCertificateNotValidNow(): Boolean { + val isNotYetValid = this is CertificateNotYetValidException + val isExpired = this is CertificateExpiredException + return isNotYetValid || isExpired +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt deleted file mode 100644 index 71d3fd173..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/ResourcesExtension.kt +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.wulkanowy.utils - -import android.content.res.Resources -import io.github.wulkanowy.R -import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException -import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException -import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException -import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException -import io.github.wulkanowy.sdk.scrapper.exception.VulcanException -import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException -import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException -import okhttp3.internal.http2.StreamResetException -import java.io.InterruptedIOException -import java.net.ConnectException -import java.net.SocketException -import java.net.SocketTimeoutException -import java.net.UnknownHostException - -fun Resources.getString(error: Throwable) = when (error) { - is UnknownHostException -> getString(R.string.error_no_internet) - is SocketException, - is SocketTimeoutException, - is InterruptedIOException, - is ConnectException, - is StreamResetException -> getString(R.string.error_timeout) - is NotLoggedInException -> getString(R.string.error_login_failed) - is PasswordChangeRequiredException -> getString(R.string.error_password_change_required) - is ServiceUnavailableException -> getString(R.string.error_service_unavailable) - is FeatureDisabledException -> getString(R.string.error_feature_disabled) - is FeatureNotAvailableException -> getString(R.string.error_feature_not_available) - is VulcanException -> getString(R.string.error_unknown_uonet) - is ScrapperException -> getString(R.string.error_unknown_app) - else -> getString(R.string.error_unknown) -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bdf2935bd..9c63073e2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -767,6 +767,7 @@ No internet connection + An error occurred. Check your device clock Connection to register failed. Servers can be overloaded. Please try again later Loading data failed. Please try again later Register password change required From 2bb2190410671dc5edaba4e541930ba1a41f72c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 1 Jan 2022 17:48:58 +0100 Subject: [PATCH 29/30] New Crowdin updates (#1745) --- app/src/main/res/values-cs/strings.xml | 1 + .../main/res/values-de/preferences_values.xml | 6 +- app/src/main/res/values-de/strings.xml | 125 +++++++++--------- app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-sk/strings.xml | 1 + app/src/main/res/values-uk/strings.xml | 1 + 7 files changed, 71 insertions(+), 65 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 3d9cdd9a7..b56617ed7 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -760,6 +760,7 @@ Aktualizace selhala! Wulkanowy nemusí fungovat správně. Zvažte aktualizaci Žádné internetové připojení + Vyskytla se chyba. Zkontrolujte hodiny svého zařízení Nelze se připojit ke deníku. Servery mohou být přetíženy. Prosím zkuste to znovu později Načítání dat se nezdařilo. Prosím zkuste to znovu později Je vyžadována změna hesla pro deník diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 23e54cb78..08b9d240b 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -41,9 +41,9 @@ Farben der Bewertungen im Logbuch
- Up to 1 at once - Always expanded - Unlimited expansions + Bis zu 1 auf einmal + Immer erweitert + Unbegrenzte Erweiterungen Durchschnitt der Noten aus beiden Semestern diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9a6e3e662..931f643a6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -6,7 +6,7 @@ Noten Schulbesuch Prüfungen - Stundenplan + Zeitplan Einstellungen Mehr Über die Applikation @@ -17,7 +17,7 @@ Lizenzen Nachrichten neue Nachricht - New homework + Neue Hausaufgaben Eintragen und Erfolgen Hausaufgaben Konten-Manager @@ -56,7 +56,7 @@ Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers Ausgewählter Student ist bereits angemeldet. Das Symbol kann auf der Registerseite in Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilnegefunden werden.\n\nStellen Sie sicher, dass Sie die entsprechende Registervariante im Feld UONET+ Registervariante auf dem vorherigen Bildschirm festgelegt haben. Wulkanowy erkennt zur Zeit keine Vorschulstudenten - Wählen Sie die Studenten aus, die sich bei der Anwendung anmelden sollen. + Wählen Sie die Studenten aus, die sich bei der Anwendung anmelden sollen Andere Optionen In diesem Modus funktioniert eine Glücknummer, eine Klassenstatistik, eine Zusammenfassung der Anwesenheit, eine Entschuldigung für die Abwesenheit, abgeschlossene Lektionen, Schulinformationen und eine Vorschau der Liste der registrierten Geräte nicht In diesem Modus werden dieselben Daten angezeigt, die auf der Klassenbuch-Website angezeigt werden @@ -95,7 +95,7 @@ Wie funktioniert der berechnete Durchschnitt? The Calculated Average is the arithmetic average calculated from the subjects averages. It allows you to know the approximate final average. It is calculated in a way selected by the user in the application settings. It is recommended that you choose the appropriate option. This is because the calculation of school averages differs. Additionally, if your school reports the average of the subjects on the Vulcan page, the application downloads them and does not calculate these averages. This can be changed by forcing the calculation of the average in the application settings.\n\nAverage of grades only from selected semester:\n1. Calculating the weighted average for each subject in a given semester\n2.Adding calculated averages\n3. Calculation of the arithmetic average of the summed averages\n\nAverage of averages from both semesters:\n1.Calculating the weighted average for each subject in semester 1 and 2\n2. Calculating the arithmetic average of the calculated averages for semesters 1 and 2 for each subject.\n3. Adding calculated averages\n4. Calculation of the arithmetic average of the summed averages\n\nAverage of grades from the whole year:\n1. Calculating weighted average over the year for each subject. The final average in the 1st semester is irrelevant.\n3. Adding calculated averages\n4. Calculating the arithmetic average of summed averages Wie funktioniert der endgültige Durchschnitt? - The Final Average is the arithmetic average calculated from all currently available final grades in the given semester.\n\nThe calculation scheme consists of the following steps:\n1. Summing up the final grades given by teachers\n2. Divide by the number of subjects that have already been graded + Der Final Average ist das arithmetische Mittel, das aus allen derzeit verfügbaren Abschlussnoten des jeweiligen Semesters berechnet wird. \n\nDas Berechnungsschema besteht aus folgenden Schritten:\n1. Zusammenfassung der von den Lehrern gegebenen Abschlussnoten\n2. Division durch die Anzahl der Fächer, die bereits bewertet wurden Finaler Durchschnitt aus %1$d von %2$d Schulfächern Zusammenfassung @@ -151,25 +151,25 @@ Jetzt: %s In einem Moment: %s Später: %s - %1$s lesson %2$d - %3$s - Change of room from %1$s to %2$s - Change of teacher from %1$s to %2$s - Change of subject from %1$s to %2$s + %1$s Lektion %2$d - %3$s + Änderung des Raumes von %1$s zu %2$s + Wechsel des Lehrers von %1$s zu %2$s + Thema von %1$s zu %2$s wechseln - Timetable change - Timetable changes + Änderung des Zeitplans + Änderungen des Zeitplans - %1$s - %2$d change in timetable - %1$s - %2$d changes in timetable + %1$s – %2$d Änderung im Zeitplan + %1$s - %2$d Änderungen im Zeitplan - %1$d change in timetable - %1$d changes in timetable + %1$d Änderung im Zeitplan + %1$d Änderungen im Zeitplan - %d change - %d changes + %d Änderung + %d Änderungen Beendete Lektionen @@ -182,17 +182,17 @@ Zusätzliche Lektionen Zusätzliche Lektionen anzeigen Keine Informationen über zusätzlichen Lektionen - New lesson - New additional lesson - Additional lesson added successfully - Additional lesson deleted successfully - Repeat weekly - Delete additional lesson - Just this lesson - All in the series - Start time - End time - End time must be greater than start time + Neue Lektion + Neue zusätzliche Lektion + Zusätzliche Lektion erfolgreich hinzugefügt + Zusätzliche Lektion erfolgreich gelöscht + Wöchentlich wiederholen + Zusätzliche Lektion löschen + Nur diese Lektion + Alle in der Reihe + Startzeit + Endzeit + Endzeit muss grösser sein als Startzeit Übersicht über die Schulbesuch Aus schulischen Gründen abwesend @@ -212,16 +212,16 @@ Sie müssen mindestens eine Abwesenheit auswählen! Verzeihung - New attendance - New attendance + Neue Teilnehmerzahl + Neue Teilnehmerzahl - %1$d new attendance - %1$d attendance + %1$d neue Teilnahme + %1$d Teilnahme - %d attendance - %d attendance + %d Teilnahme + %d Teilnahme Gesamt @@ -234,8 +234,8 @@ Neue prüfungen
- %d new exam - %d new exams + %d neue Prüfung + %d neue Prüfungen %d prüfung @@ -327,9 +327,9 @@ Keine Informationen über Hausaufgaben Gemacht Unvollständig - Add homework - Homework added successfully - Homework deleted successfully + Hausaufgaben hinzufügen + Hausaufgaben erfolgreich hinzugefügt + Heimarbeit erfolgreich gelöscht Anhänge Neue hausaufgaben @@ -483,7 +483,7 @@ Lektionen (Morgen) - (Today and tomorrow) + (Heute und morgen) Gleich: Bald: Erstens: @@ -557,7 +557,7 @@ Nein Speichern Titel - Add + Hinzufügen Kopiert lösen Ändern @@ -574,7 +574,7 @@ Mittelwertberechnung durch App erzwingen Anwesendheit zeigen Thema - Grades expanding + Steigende Sorten Aktuelle Lektion markieren Gruppen neben Schulfächen anzeigen Liste der Diagramme in Klassenbewertungen anzeigen @@ -583,7 +583,7 @@ Schulfachen sortieren Sprache Benachrichtigungen - Other + Sonstiges Benachrichtigungen anzeigen Benachrichtigungen über bevorstehende Lektionen anzeigen Festlegen einer Benachrichtigung über die bevorstehende Lektion dauerhaft @@ -593,14 +593,14 @@ Ihr Gerät hat möglicherweise Probleme mit der Datensynchronisierung und Benachrichtigungen.\n\nUm diese zu reparieren, fügen Sie Wulkanowy zum Autostart hinzu und deaktivieren Sie die Batterieoptimierung in den Systemeinstellungen des Geräts. Debug-Benachrichtigungen anzeigen Synchronisierung ist deaktiviert - Official app notifications + Offizielle Benachrichtigungen Offizielle App-Benachrichtigungen erfassen - Remove official app notifications after capture + Entfernen Sie offizielle App-Benachrichtigungen nach der Erfassung Benachrichtigungen erfassen With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY - Upcoming lesson notifications - You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature. - Go to settings + Bevorstehende Unterrichtsbenachrichtigungen + Sie müssen der Wulkanowy-App erlauben, in Ihren Systemeinstellungen Alarme und Erinnerungen einzustellen, damit diese Funktion verwendet werden kann. + Gehe zu den Einstellungen Synchronisierung Automatische Aktualisierung An Feiertagen suspendiert @@ -615,26 +615,26 @@ Wert des Minus Antwort mit Nachrichtenhistorie Arithmetisches Mittel anzeigen, wenn keine Gewichte angegeben sind - Support - Watch single ad to support project - Consent to data processing - To view an advertisement you must agree to the data processing terms of our Privacy Policy - Agree - Privacy policy - Ad is loading - Thank you for your support, come back later for more ads + Unterstützung + Einzelanzeige ansehen, um Projekt zu unterstützen + Einwilligung in die Datenverarbeitung + Um eine Anzeige zu sehen, müssen Sie mit den Datenverarbeitungsbedingungen unserer Datenschutzerklärung einverstanden sein + Einverstanden + Datenschutzerklärung + Anzeige wird geladen + Vielen Dank für Ihre Unterstützung, kommen Sie später wieder für weitere Anzeigen Erweitert Aussehen & Verhalten Benachrichtigungen Synchronisierung - Advertisements + Werbung Noten Dashboard Sichtbarkeit der Kacheln Schulbesuch Stundenplan Noten - Calculated average + Berechneter Durchschnitt Nachrichten Aussehen & Verhalten Sprachen, Themen, Schulfachen sortieren @@ -644,8 +644,8 @@ Automatisches Update, Synchronisierungsintervall Plus und Minus Werte, Durchschnittsberechnung Erweitert - App version, contributors, social portals - Displaying advertisements, project support + App-Version, Mitwirkende, soziale Portale + Anzeigen, Projektunterstützung Neue Noten Neue Hausaufgaben @@ -658,8 +658,8 @@ Push-Benachrichtigungen Bevorstehende Lektionen Debuggen - Timetable change - New attendance + Änderung des Zeitplans + Neue Teilnehmerzahl Schwarz Rot @@ -674,6 +674,7 @@ Update fehlgeschlagen! Wulkanowy funktioniert möglicherweise nicht richtig. Überlegen Sie die Aktualisierung Keine Internetverbindung + Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr Registrierungsverbindung fehlgeschlagen. Server können überlastet sein. Bitte versuchen Sie es später noch einmal Das Laden der Daten ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal Passwortänderung für Registrierung erforderlich @@ -683,5 +684,5 @@ Ein unerwarteter Fehler ist aufgetreten Funktion, die von Ihrer Schule deaktiviert wurde Feature in diesem Modus nicht verfügbar - This field is required + Dieses Feld ist erforderlich diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ff21b6446..208e6f3ea 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -760,6 +760,7 @@ Aktualizacja nie powiodła się! Wulkanowy może nie działać prawidłowo. Rozważ aktualizację Brak połączenia z internetem + Wystąpił błąd. Sprawdź poprawność daty w urządzeniu Nie udało się połączyć z dziennikiem. Serwery mogą być przeciążone. Spróbuj ponownie później Ładowanie danych nie powiodło się. Spróbuj ponownie później Wymagana zmiana hasła do dziennika diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 87cae3ea7..b5ad3683e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -760,6 +760,7 @@ Не удалось обновить! Wulkanowy может работать некорректно. Рассмотрите возможность обновления Нет интернет-подключения + An error occurred. Check your device clock Не удалось подключиться к регистрации. Серверы могут быть перегружены. Пожалуйста, повторите попытку позже Не удалось загрузить данные. Пожалуйста, повторите попытку позже Необходимо изменить пароль реестра diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 3698fce92..30365fe56 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -760,6 +760,7 @@ Aktualizácia zlyhala! Wulkanowy nemusí fungovať správne. Zvážte aktualizáciu Žiadne internetové pripojenie + Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia Nedá sa pripojiť ku denníku. Servery môžu byť preťažené. Prosím skúste to znova neskôr Načítanie údajov zlyhalo. Skúste neskôr prosím Je vyžadovaná zmena hesla pre denník diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f3f749cf9..0c88df317 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -760,6 +760,7 @@ Помилка оновлення! Wulkanowy може не працювати належним чином. Подумайте про оновлення Брак з\'єднання з інтернетом + An error occurred. Check your device clock Помилка підключення до реєстрації. Сервери можуть бути перевантажені. Будь-ласка спробуйте пізніше Помилка завантаження даних. Будь-ласка спробуйте пізніше Потрібна реєстрація зміни пароля From daf97be9ad7e907cda532069c60162039a02615f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 1 Jan 2022 17:50:02 +0100 Subject: [PATCH 30/30] Version 1.5.0 --- app/build.gradle | 12 +++++++----- app/src/main/play/release-notes/pl-PL/default.txt | 8 +++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 840229297..41be0d50e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,8 +22,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 31 - versionCode 102 - versionName "1.4.4" + versionCode 103 + versionName "1.5.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -149,8 +149,10 @@ kapt { play { defaultToAppBundles = false - track = 'beta' - updatePriority = 4 + track = 'production' + releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS + userFraction = 0.25d + updatePriority = 1 enabled.set(false) } @@ -174,7 +176,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:6e1b8eb26a" + implementation "io.github.wulkanowy:sdk:1.5.0" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index f40e3fc33..6e768ac3a 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,8 @@ -Wersja 1.4.4 +Wersja 1.5.0 -- naprawiliśmy logowanie do Gdańskiej Platformy Edukacyjnej -- naprawiliśmy sortowanie ocen oraz ogłoszeń +- dodaliśmy możliwość dodawania własnych lekcji dodatkowych +- dodaliśmy wsparcie dla różnych stref czasowych +- dodaliśmy eksperymentalne wsparcie dla przedszkola +- wprowadziliśmy też wiele innych mniejszych poprawek, poprawiających komfort używania aplikacji Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases