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 001/105] 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 002/105] 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 003/105] 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 004/105] 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 005/105] 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 006/105] 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 007/105] 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 008/105] 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 009/105] 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 010/105] 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 011/105] 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 012/105] 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 013/105] 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 014/105] 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 015/105] 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 016/105] 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 017/105] 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 018/105] 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 019/105] 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 020/105] 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 021/105] 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 022/105] 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 023/105] 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 024/105] 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 025/105] 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 026/105] 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 027/105] 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 028/105] 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 029/105] 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 030/105] 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 From d5cc2263f52378c9d285fabe1e734cd30916852c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jan 2022 10:13:31 +0000 Subject: [PATCH 031/105] Bump mockk from 1.12.1 to 1.12.2 (#1747) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 41be0d50e..edf96b7ff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -171,7 +171,7 @@ ext { android_hilt = "1.0.0" room = "2.4.0" chucker = "3.5.2" - mockk = "1.12.1" + mockk = "1.12.2" coroutines = "1.6.0" } From 210308695b1fd2ca12839edeff9ab6297b5cb020 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jan 2022 11:12:48 +0000 Subject: [PATCH 032/105] Bump huawei-publish-gradle-plugin from 1.3.0 to 1.3.1 (#1750) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b06af2e16..b91320f7d 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { 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" + classpath "ru.cian:huawei-publish-gradle-plugin:1.3.1" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" From 14ebdad7b24dea72b41a35b965a0e20288eed5ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 00:55:58 +0000 Subject: [PATCH 033/105] Bump constraintlayout from 2.1.2 to 2.1.3 (#1761) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index edf96b7ff..39a63eab3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -194,7 +194,7 @@ dependencies { implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" - implementation "androidx.constraintlayout:constraintlayout:2.1.2" + implementation "androidx.constraintlayout:constraintlayout:2.1.3" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "com.google.android.material:material:1.4.0" implementation "com.github.wulkanowy:material-chips-input:2.3.1" From c9b506ae10d4fb14bb8d77ce7acf75f8b46e8cfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 00:56:16 +0000 Subject: [PATCH 034/105] Bump agcp from 1.6.3.200 to 1.6.3.300 (#1760) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b91320f7d..6c0e709d2 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.3.200' + classpath 'com.huawei.agconnect:agcp:1.6.3.300' 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.1" From 90e1cea679dcbc1b7b3f496425ec7cc18804c534 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 00:56:37 +0000 Subject: [PATCH 035/105] Bump appcompat from 1.4.0 to 1.4.1 (#1759) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 39a63eab3..ec2447352 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ dependencies { implementation "androidx.core:core-ktx:1.7.0" implementation 'androidx.core:core-splashscreen:1.0.0-alpha02' implementation "androidx.activity:activity-ktx:1.4.0" - implementation "androidx.appcompat:appcompat:1.4.0" + implementation "androidx.appcompat:appcompat:1.4.1" implementation "androidx.fragment:fragment-ktx:1.4.0" implementation "androidx.annotation:annotation:1.3.0" From 5146e44574888aadb012e18ec8c81a33c362d3b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 00:56:57 +0000 Subject: [PATCH 036/105] Bump agconnect-crash from 1.6.3.200 to 1.6.3.300 (#1758) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ec2447352..5a66b4c4c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -240,7 +240,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.3.200' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.3.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From b52a6f7f61e81dc93432732dde89ea519434c10a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 00:57:17 +0000 Subject: [PATCH 037/105] Bump room from 2.4.0 to 2.4.1 (#1755) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5a66b4c4c..2c0e33fd8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -169,7 +169,7 @@ huaweiPublish { ext { work_manager = "2.7.1" android_hilt = "1.0.0" - room = "2.4.0" + room = "2.4.1" chucker = "3.5.2" mockk = "1.12.2" coroutines = "1.6.0" From a00f2dcbda5194bf96e9cd43e349ef44d37de818 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 00:57:40 +0000 Subject: [PATCH 038/105] Bump core from 1.10.2 to 1.10.3 (#1753) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2c0e33fd8..4751e9ce2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -235,7 +235,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' - playImplementation 'com.google.android.play:core:1.10.2' + playImplementation 'com.google.android.play:core:1.10.3' playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:20.5.0' From 5d1085a64abad49066606b2ba1c71b07cfc861dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 00:58:12 +0000 Subject: [PATCH 039/105] Bump fuzzywuzzy from 1.3.1 to 1.3.3 (#1754) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 4751e9ce2..d0866b74b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -228,7 +228,7 @@ dependencies { implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "io.coil-kt:coil:1.4.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" - implementation 'me.xdrop:fuzzywuzzy:1.3.1' + implementation 'me.xdrop:fuzzywuzzy:1.3.3' implementation 'com.fredporciuncula:flow-preferences:1.6.0' playImplementation platform('com.google.firebase:firebase-bom:29.0.3') From ce4157933fa5ccf30bcf5d2602625a5397223078 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 01:39:16 +0000 Subject: [PATCH 040/105] Bump core-splashscreen from 1.0.0-alpha02 to 1.0.0-beta01 (#1752) --- app/build.gradle | 2 +- .../io/github/wulkanowy/ui/modules/splash/SplashActivity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d0866b74b..96138883f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -184,7 +184,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "androidx.core:core-ktx:1.7.0" - implementation 'androidx.core:core-splashscreen:1.0.0-alpha02' + implementation 'androidx.core:core-splashscreen:1.0.0-beta01' implementation "androidx.activity:activity-ktx:1.4.0" implementation "androidx.appcompat:appcompat:1.4.1" implementation "androidx.fragment:fragment-ktx:1.4.0" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt index 5c1524551..a86024e49 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt @@ -42,7 +42,7 @@ class SplashActivity : BaseActivity(), SplashView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - installSplashScreen().setKeepVisibleCondition { true } + installSplashScreen().setKeepOnScreenCondition { true } val externalLink = intent?.getStringExtra(EXTRA_EXTERNAL_URL) val startDestination = intent?.getSerializableExtra(EXTRA_START_DESTINATION) as Destination? From 513b4b7d3edbd6ee3a4d10129a5f80856cf7f652 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 01:43:30 +0000 Subject: [PATCH 041/105] Bump coordinatorlayout from 1.1.0 to 1.2.0 (#1756) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 96138883f..95c2178d8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ dependencies { implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.constraintlayout:constraintlayout:2.1.3" - implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" + implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" implementation "com.google.android.material:material:1.4.0" implementation "com.github.wulkanowy:material-chips-input:2.3.1" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" From 7a9ba04ff49d988b956ef96c1d54d16693621840 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 02:17:14 +0000 Subject: [PATCH 042/105] Bump material from 1.4.0 to 1.5.0 (#1757) --- app/build.gradle | 2 +- .../wulkanowy/ui/modules/main/MainActivity.kt | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 95c2178d8..b4598ffa2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.constraintlayout:constraintlayout:2.1.3" implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" - implementation "com.google.android.material:material:1.4.0" + implementation "com.google.android.material:material:1.5.0" implementation "com.github.wulkanowy:material-chips-input:2.3.1" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation 'com.github.lopspower:CircularImageView:4.2.0' diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index d81abe345..02af02adc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -23,16 +23,7 @@ import io.github.wulkanowy.databinding.ActivityMainBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.InAppReviewHelper -import io.github.wulkanowy.utils.UpdateHelper -import io.github.wulkanowy.utils.createNameInitialsDrawable -import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.nickOrName -import io.github.wulkanowy.utils.safelyPopFragments -import io.github.wulkanowy.utils.setOnViewChangeListener +import io.github.wulkanowy.utils.* import timber.log.Timber import javax.inject.Inject @@ -169,8 +160,12 @@ class MainActivity : BaseActivity(), MainVie .setIcon(R.drawable.ic_main_more) } selectedItemId = startMenuIndex - setOnItemSelectedListener { presenter.onTabSelected(it.itemId, false) } - setOnItemReselectedListener { presenter.onTabSelected(it.itemId, true) } + setOnItemSelectedListener { + this@MainActivity.presenter.onTabSelected(it.itemId, false) + } + setOnItemReselectedListener { + this@MainActivity.presenter.onTabSelected(it.itemId, true) + } } } From e1d82d70ee6abdbca2c99081d1124fd54ed66c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 23 Jan 2022 20:04:01 +0100 Subject: [PATCH 043/105] New translations strings.xml (German) (#1746) --- app/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 931f643a6..5b2fee80d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -93,7 +93,7 @@ Vorhergesagte Note Berechnender Durchschnitt 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 + Der berechnete Mittelwert ist das arithmetische Mittel, das aus den Durchschnittswerten der Probanden errechnet wird. Es erlaubt Ihnen, den ungefähre endgültigen Durchschnitt zu kennen. Sie wird auf eine vom Anwender in den Anwendungseinstellungen gewählte Weise berechnet. Es wird empfohlen, die entsprechende Option zu wählen. Das liegt daran, dass die Berechnung der Schuldurchschnitte unterschiedlich ist. Wenn Ihre Schule den Durchschnitt der Fächer auf der Vulcan-Seite angibt, lädt die Anwendung diese Fächer herunter und berechnet nicht den Durchschnitt. Dies kann geändert werden, indem die Berechnung des Durchschnitts in den Anwendungseinstellungen erzwungen wird. \n\nDurchschnitt der Noten nur aus dem ausgewählten Semester :\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in einem bestimmten Semester\n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Durchschnitte aus beiden Semestern:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in Semester 1 und 2\n2. Berechnung des arithmetischen Mittels der berechneten Durchschnitte für Semester 1 und 2 für jedes Fach. \n3. Hinzufügen von berechneten Durchschnittswerten\n4. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Noten aus dem ganzen Jahr:\n1. Berechnung des gewichteten Jahresdurchschnitts für jedes Fach. Der Abschlussdurchschnitt im 1. Semester ist irrelevant. \n3. Addition der berechneten Durchschnittswerte\n4. Berechnung des arithmetischen Mittels der summierten Mittelwerte Wie funktioniert der endgültige Durchschnitt? 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 From 009ec433be0476f5984aa7ac2a566a16e326c4b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Jan 2022 12:06:30 +0000 Subject: [PATCH 044/105] Bump firebase-bom from 29.0.3 to 29.0.4 (#1766) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b4598ffa2..ed3f964fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -231,7 +231,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.3.3' implementation 'com.fredporciuncula:flow-preferences:1.6.0' - playImplementation platform('com.google.firebase:firebase-bom:29.0.3') + playImplementation platform('com.google.firebase:firebase-bom:29.0.4') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 40e093450418da24578e97797664dadf751ba77a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jan 2022 11:07:22 +0000 Subject: [PATCH 045/105] Bump fuzzywuzzy from 1.3.3 to 1.4.0 (#1765) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ed3f964fd..affe89298 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -228,7 +228,7 @@ dependencies { implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "io.coil-kt:coil:1.4.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" - implementation 'me.xdrop:fuzzywuzzy:1.3.3' + implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.6.0' playImplementation platform('com.google.firebase:firebase-bom:29.0.4') From de9fcb9af95cf85a16cc847f6f0da882a1f84260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 28 Jan 2022 12:07:48 +0100 Subject: [PATCH 046/105] Add what-the-stack library (#1767) --- app/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle b/app/build.gradle index affe89298..498673416 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -246,6 +246,7 @@ dependencies { debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6' + debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha02' testImplementation "junit:junit:4.13.2" testImplementation "io.mockk:mockk:$mockk" From daa7b54dab180ea9be1d5551dda2eb2979cbaf6f Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Fri, 28 Jan 2022 13:43:56 +0100 Subject: [PATCH 047/105] Refactor notification destinations (#1709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Borcz --- .../48.json | 2445 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 71 +- .../io/github/wulkanowy/data/db/Converters.kt | 10 + .../data/db/entities/Notification.kt | 4 + .../wulkanowy/data/pojos/NotificationData.kt | 6 +- .../data/serializers/LocalDateSerializer.kt | 32 + .../services/shortcuts/ShortcutsHelper.kt | 3 + .../notifications/AppNotificationManager.kt | 8 +- .../ChangeTimetableNotification.kt | 14 +- .../NewAttendanceNotification.kt | 4 +- .../NewConferenceNotification.kt | 4 +- .../sync/notifications/NewExamNotification.kt | 4 +- .../notifications/NewGradeNotification.kt | 12 +- .../notifications/NewHomeworkNotification.kt | 4 +- .../NewLuckyNumberNotification.kt | 2 +- .../notifications/NewMessageNotification.kt | 4 +- .../sync/notifications/NewNoteNotification.kt | 4 +- .../NewSchoolAnnouncementNotification.kt | 10 +- .../wulkanowy/ui/modules/Destination.kt | 80 +- .../debug/notification/mock/timetable.kt | 4 +- .../NotificationsCenterAdapter.kt | 5 +- .../NotificationsCenterFragment.kt | 33 +- .../NotificationsCenterPresenter.kt | 2 +- .../services/messaging/AppMessagingService.kt | 2 + 24 files changed, 2578 insertions(+), 189 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/48.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/serializers/LocalDateSerializer.kt diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/48.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/48.json new file mode 100644 index 000000000..1c11aae91 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/48.json @@ -0,0 +1,2445 @@ +{ + "formatVersion": 1, + "database": { + "version": 48, + "identityHash": "95751b933ad9f835ffc1805f4ef71bdb", + "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, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `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": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "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, '95751b933ad9f835ffc1805f4ef71bdb')" + ] + } +} \ 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 24bb3917e..379b8738f 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 @@ -1,72 +1,10 @@ package io.github.wulkanowy.data.db import android.content.Context -import androidx.room.AutoMigration -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase +import androidx.room.* import androidx.room.RoomDatabase.JournalMode.TRUNCATE -import androidx.room.TypeConverters -import io.github.wulkanowy.data.db.dao.AdminMessageDao -import io.github.wulkanowy.data.db.dao.AttendanceDao -import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao -import io.github.wulkanowy.data.db.dao.CompletedLessonsDao -import io.github.wulkanowy.data.db.dao.ConferenceDao -import io.github.wulkanowy.data.db.dao.ExamDao -import io.github.wulkanowy.data.db.dao.GradeDao -import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao -import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao -import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao -import io.github.wulkanowy.data.db.dao.GradeSummaryDao -import io.github.wulkanowy.data.db.dao.HomeworkDao -import io.github.wulkanowy.data.db.dao.LuckyNumberDao -import io.github.wulkanowy.data.db.dao.MessageAttachmentDao -import io.github.wulkanowy.data.db.dao.MessagesDao -import io.github.wulkanowy.data.db.dao.MobileDeviceDao -import io.github.wulkanowy.data.db.dao.NoteDao -import io.github.wulkanowy.data.db.dao.NotificationDao -import io.github.wulkanowy.data.db.dao.RecipientDao -import io.github.wulkanowy.data.db.dao.ReportingUnitDao -import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao -import io.github.wulkanowy.data.db.dao.SchoolDao -import io.github.wulkanowy.data.db.dao.SemesterDao -import io.github.wulkanowy.data.db.dao.StudentDao -import io.github.wulkanowy.data.db.dao.StudentInfoDao -import io.github.wulkanowy.data.db.dao.SubjectDao -import io.github.wulkanowy.data.db.dao.TeacherDao -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.AdminMessage -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.AttendanceSummary -import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.data.db.entities.Conference -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.db.entities.GradePartialStatistics -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics -import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.MessageAttachment -import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.db.entities.Note -import io.github.wulkanowy.data.db.entities.Notification -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.School -import io.github.wulkanowy.data.db.entities.SchoolAnnouncement -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.StudentInfo -import io.github.wulkanowy.data.db.entities.Subject -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.dao.* +import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.migrations.* import io.github.wulkanowy.utils.AppInfo import javax.inject.Singleton @@ -108,6 +46,7 @@ import javax.inject.Singleton autoMigrations = [ AutoMigration(from = 44, to = 45), AutoMigration(from = 46, to = 47), + AutoMigration(from = 47, to = 48), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -116,7 +55,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 47 + const val VERSION_SCHEMA = 48 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), 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 b7013a32a..9d3beae1f 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,11 +1,14 @@ package io.github.wulkanowy.data.db import androidx.room.TypeConverter +import io.github.wulkanowy.ui.modules.Destination 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.* +import java.util.* import java.time.Instant import java.time.LocalDate import java.time.Month @@ -58,4 +61,11 @@ class Converters { emptyList() // handle errors from old gson Pair serialized data } } + + @TypeConverter + fun destinationToString(destination: Destination) = json.encodeToString(destination) + + @TypeConverter + fun stringToDestination(destination: String): Destination = json.decodeFromString(destination) + } 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 4867e3329..c3267f24e 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,6 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import io.github.wulkanowy.services.sync.notifications.NotificationType +import io.github.wulkanowy.ui.modules.Destination import java.time.Instant @Entity(tableName = "Notifications") @@ -18,6 +19,9 @@ data class Notification( val type: NotificationType, + @ColumnInfo(defaultValue = "{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}") + val destination: Destination, + val date: Instant, val data: String? = null diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt index 0748ba647..f4fd0fc8a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt @@ -1,10 +1,10 @@ package io.github.wulkanowy.data.pojos -import android.content.Intent import io.github.wulkanowy.services.sync.notifications.NotificationType +import io.github.wulkanowy.ui.modules.Destination data class NotificationData( - val intentToStart: Intent, + val destination: Destination, val title: String, val content: String ) @@ -13,7 +13,7 @@ data class GroupNotificationData( val notificationDataList: List, val title: String, val content: String, - val intentToStart: Intent, + val destination: Destination, val type: NotificationType ) diff --git a/app/src/main/java/io/github/wulkanowy/data/serializers/LocalDateSerializer.kt b/app/src/main/java/io/github/wulkanowy/data/serializers/LocalDateSerializer.kt new file mode 100644 index 000000000..ba97d37a2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/serializers/LocalDateSerializer.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.data.serializers + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.nullable +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.time.LocalDate + +@OptIn(ExperimentalSerializationApi::class) +object LocalDateSerializer : KSerializer { + + override val descriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG).nullable + + override fun serialize(encoder: Encoder, value: LocalDate?) { + if (value == null) { + encoder.encodeNull() + } else { + encoder.encodeNotNullMark() + encoder.encodeLong(value.toEpochDay()) + } + } + + override fun deserialize(decoder: Decoder): LocalDate? = + if (decoder.decodeNotNullMark()) { + LocalDate.ofEpochDay(decoder.decodeLong()) + } else { + decoder.decodeNull() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt index 4ad9ac120..ee31af46b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt @@ -15,6 +15,9 @@ import javax.inject.Singleton @Singleton class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) { + // Destination cannot be used here as shortcuts + // require their intents to only use primitive types (see PersistableBundle.isValidType). + private val destinations = mapOf( "grade" to Destination.Grade, "attendance" to Destination.Attendance, 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 7ac532aeb..dadb68c50 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 @@ -14,6 +14,7 @@ import io.github.wulkanowy.data.pojos.GroupNotificationData import io.github.wulkanowy.data.pojos.NotificationData import io.github.wulkanowy.data.repositories.NotificationRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.getCompatBitmap import io.github.wulkanowy.utils.getCompatColor @@ -47,7 +48,7 @@ class AppNotificationManager @Inject constructor( PendingIntent.getActivity( context, Random.nextInt(), - notificationData.intentToStart, + SplashActivity.getStartIntent(context, notificationData.destination), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) ) @@ -92,7 +93,7 @@ class AppNotificationManager @Inject constructor( PendingIntent.getActivity( context, Random.nextInt(), - notificationData.intentToStart, + SplashActivity.getStartIntent(context, notificationData.destination), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) ) @@ -146,7 +147,7 @@ class AppNotificationManager @Inject constructor( PendingIntent.getActivity( context, Random.nextInt(), - groupNotificationData.intentToStart, + SplashActivity.getStartIntent(context, groupNotificationData.destination), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) ) @@ -168,6 +169,7 @@ class AppNotificationManager @Inject constructor( studentId = student.id, title = notificationData.title, content = notificationData.content, + destination = notificationData.destination, type = notificationType, date = Instant.now(), ) 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 b1f9a7b06..43ae1fea9 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 @@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.pojos.GroupNotificationData import io.github.wulkanowy.data.pojos.NotificationData 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 @@ -23,8 +22,9 @@ class ChangeTimetableNotification @Inject constructor( suspend fun notify(items: List, student: Student) { val currentTime = Instant.now() val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime } - val notificationDataList = changedLessons.groupBy { it.date } - .map { (date, lessons) -> + val lessonsByDate = changedLessons.groupBy { it.date } + val notificationDataList = lessonsByDate + .flatMap { (date, lessons) -> getNotificationContents(date, lessons).map { NotificationData( title = context.getPlural( @@ -32,14 +32,10 @@ class ChangeTimetableNotification @Inject constructor( 1 ), content = it, - intentToStart = SplashActivity.getStartIntent( - context = context, - destination = Destination.Timetable(date) - ) + destination = Destination.Timetable(date) ) } } - .flatten() .ifEmpty { return } val groupNotificationData = GroupNotificationData( @@ -53,7 +49,7 @@ class ChangeTimetableNotification @Inject constructor( changedLessons.size, changedLessons.size ), - intentToStart = SplashActivity.getStartIntent(context, Destination.Timetable()), + destination = Destination.Timetable(lessonsByDate.toSortedMap().firstKey()), type = NotificationType.CHANGE_TIMETABLE ) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt index c78dcd053..49842c9a6 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt @@ -31,7 +31,7 @@ class NewAttendanceNotification @Inject constructor( NotificationData( title = context.getPlural(R.plurals.attendance_notify_new_items_title, 1), content = it, - intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance) + destination = Destination.Attendance ) } @@ -46,7 +46,7 @@ class NewAttendanceNotification @Inject constructor( notificationDataList.size, notificationDataList.size ), - intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance), + destination = Destination.Attendance, type = NotificationType.NEW_ATTENDANCE ) 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 d27c57285..92977ebb1 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 @@ -31,7 +31,7 @@ class NewConferenceNotification @Inject constructor( NotificationData( title = context.getPlural(R.plurals.conference_notify_new_item_title, 1), content = it, - intentToStart = SplashActivity.getStartIntent(context, Destination.Conference) + destination = Destination.Conference ) } @@ -43,7 +43,7 @@ class NewConferenceNotification @Inject constructor( lines.size, lines.size ), - intentToStart = SplashActivity.getStartIntent(context, Destination.Conference), + destination = Destination.Conference, type = NotificationType.NEW_CONFERENCE ) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt index b3cf04c41..125bbf92d 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt @@ -31,7 +31,7 @@ class NewExamNotification @Inject constructor( NotificationData( title = context.getPlural(R.plurals.exam_notify_new_item_title, 1), content = it, - intentToStart = SplashActivity.getStartIntent(context, Destination.Exam), + destination = Destination.Exam, ) } @@ -43,7 +43,7 @@ class NewExamNotification @Inject constructor( lines.size, lines.size ), - intentToStart = SplashActivity.getStartIntent(context, Destination.Exam), + destination = Destination.Exam, type = NotificationType.NEW_EXAM ) 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 32d2ba6a0..9b49ed178 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 @@ -26,7 +26,7 @@ class NewGradeNotification @Inject constructor( append("${it.subject}: ${it.entry}") if (it.comment.isNotBlank()) append(" (${it.comment})") }, - intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), + destination = Destination.Grade, ) } @@ -34,7 +34,7 @@ class NewGradeNotification @Inject constructor( notificationDataList = notificationDataList, title = context.getPlural(R.plurals.grade_new_items, items.size), content = context.getPlural(R.plurals.grade_notify_new_items, items.size, items.size), - intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), + destination = Destination.Grade, type = NotificationType.NEW_GRADE_DETAILS ) @@ -46,7 +46,7 @@ class NewGradeNotification @Inject constructor( NotificationData( title = context.getPlural(R.plurals.grade_new_items_predicted, 1), content = "${it.subject}: ${it.predictedGrade}", - intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), + destination = Destination.Grade, ) } @@ -58,7 +58,7 @@ class NewGradeNotification @Inject constructor( items.size, items.size ), - intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), + destination = Destination.Grade, type = NotificationType.NEW_GRADE_PREDICTED ) @@ -70,7 +70,7 @@ class NewGradeNotification @Inject constructor( NotificationData( title = context.getPlural(R.plurals.grade_new_items_final, 1), content = "${it.subject}: ${it.finalGrade}", - intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), + destination = Destination.Grade, ) } @@ -82,7 +82,7 @@ class NewGradeNotification @Inject constructor( items.size, items.size ), - intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), + destination = Destination.Grade, type = NotificationType.NEW_GRADE_FINAL ) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt index ff32aa66f..856c51581 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt @@ -31,7 +31,7 @@ class NewHomeworkNotification @Inject constructor( NotificationData( title = context.getPlural(R.plurals.homework_notify_new_item_title, 1), content = it, - intentToStart = SplashActivity.getStartIntent(context, Destination.Homework), + destination = Destination.Homework, ) } @@ -42,7 +42,7 @@ class NewHomeworkNotification @Inject constructor( lines.size, lines.size ), - intentToStart = SplashActivity.getStartIntent(context, Destination.Homework), + destination = Destination.Homework, type = NotificationType.NEW_HOMEWORK, notificationDataList = notificationDataList ) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt index 5c36a06c0..bbe9b8a18 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt @@ -22,7 +22,7 @@ class NewLuckyNumberNotification @Inject constructor( R.string.lucky_number_notify_new_item, item.luckyNumber.toString() ), - intentToStart = SplashActivity.getStartIntent(context, Destination.LuckyNumber) + destination = Destination.LuckyNumber ) appNotificationManager.sendSingleNotification( diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt index b98d34668..5c3c52c5b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt @@ -22,7 +22,7 @@ class NewMessageNotification @Inject constructor( NotificationData( title = context.getPlural(R.plurals.message_new_items, 1), content = "${it.sender}: ${it.subject}", - intentToStart = SplashActivity.getStartIntent(context, Destination.Message), + destination = Destination.Message, ) } @@ -30,7 +30,7 @@ class NewMessageNotification @Inject constructor( notificationDataList = notificationDataList, title = context.getPlural(R.plurals.message_new_items, items.size), content = context.getPlural(R.plurals.message_notify_new_items, items.size, items.size), - intentToStart = SplashActivity.getStartIntent(context, Destination.Message), + destination = Destination.Message, type = NotificationType.NEW_MESSAGE ) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt index 65520e01b..dae7d4330 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt @@ -29,13 +29,13 @@ class NewNoteNotification @Inject constructor( NotificationData( title = context.getPlural(titleRes, 1), content = "${it.teacher}: ${it.category}", - intentToStart = SplashActivity.getStartIntent(context, Destination.Note), + destination = Destination.Note, ) } val groupNotificationData = GroupNotificationData( notificationDataList = notificationDataList, - intentToStart = SplashActivity.getStartIntent(context, Destination.Note), + destination = Destination.Note, title = context.getPlural(R.plurals.note_new_items, items.size), content = context.getPlural(R.plurals.note_notify_new_items, items.size, items.size), type = NotificationType.NEW_NOTE 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 695438a70..cc7e46564 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 @@ -21,10 +21,7 @@ class NewSchoolAnnouncementNotification @Inject constructor( suspend fun notify(items: List, student: Student) { val notificationDataList = items.map { NotificationData( - intentToStart = SplashActivity.getStartIntent( - context = context, - destination = Destination.SchoolAnnouncement - ), + destination = Destination.SchoolAnnouncement, title = context.getPlural( R.plurals.school_announcement_notify_new_item_title, 1 @@ -34,10 +31,7 @@ class NewSchoolAnnouncementNotification @Inject constructor( } val groupNotificationData = GroupNotificationData( type = NotificationType.NEW_ANNOUNCEMENT, - intentToStart = SplashActivity.getStartIntent( - context = context, - destination = Destination.SchoolAnnouncement - ), + destination = Destination.SchoolAnnouncement, title = context.getPlural( R.plurals.school_announcement_notify_new_item_title, items.size diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt index 43d4b5f9f..f49c48891 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules import androidx.fragment.app.Fragment +import io.github.wulkanowy.data.serializers.LocalDateSerializer import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment @@ -14,18 +15,19 @@ import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment -import java.io.Serializable +import kotlinx.serialization.Serializable import java.time.LocalDate -sealed interface Destination : Serializable { +@Serializable +sealed class Destination private constructor() : java.io.Serializable { /* Type in children classes have to be as getter to avoid null in enums https://stackoverflow.com/questions/68866453/kotlin-enum-val-is-returning-null-despite-being-set-at-compile-time */ - val type: Type + abstract val type: Type - val fragment: Fragment + abstract val fragment: Fragment enum class Type(val defaultDestination: Destination) { DASHBOARD(Dashboard), @@ -43,94 +45,84 @@ sealed interface Destination : Serializable { MESSAGE(Message); } - object Dashboard : Destination { - + @Serializable + object Dashboard : Destination() { override val type get() = Type.DASHBOARD - override val fragment get() = DashboardFragment.newInstance() } - object Grade : Destination { - + @Serializable + object Grade : Destination() { override val type get() = Type.GRADE - override val fragment get() = GradeFragment.newInstance() } - object Attendance : Destination { - + @Serializable + object Attendance : Destination() { override val type get() = Type.ATTENDANCE - override val fragment get() = AttendanceFragment.newInstance() } - object Exam : Destination { - + @Serializable + object Exam : Destination() { override val type get() = Type.EXAM - override val fragment get() = ExamFragment.newInstance() } - data class Timetable(val date: LocalDate? = null) : Destination { - + @Serializable + data class Timetable( + @Serializable(with = LocalDateSerializer::class) + private val date: LocalDate? = null + ) : Destination() { override val type get() = Type.TIMETABLE - override val fragment get() = TimetableFragment.newInstance(date) } - object Homework : Destination { - + @Serializable + object Homework : Destination() { override val type get() = Type.HOMEWORK - override val fragment get() = HomeworkFragment.newInstance() } - object Note : Destination { - + @Serializable + object Note : Destination() { override val type get() = Type.NOTE - override val fragment get() = NoteFragment.newInstance() } - object Conference : Destination { - + @Serializable + object Conference : Destination() { override val type get() = Type.CONFERENCE - override val fragment get() = ConferenceFragment.newInstance() } - object SchoolAnnouncement : Destination { - + @Serializable + object SchoolAnnouncement : Destination() { override val type get() = Type.SCHOOL_ANNOUNCEMENT - override val fragment get() = SchoolAnnouncementFragment.newInstance() } - object School : Destination { - + @Serializable + object School : Destination() { override val type get() = Type.SCHOOL - override val fragment get() = SchoolFragment.newInstance() } - object LuckyNumber : Destination { - + @Serializable + object LuckyNumber : Destination() { override val type get() = Type.LUCKY_NUMBER - override val fragment get() = LuckyNumberFragment.newInstance() } - object More : Destination { - + @Serializable + object More : Destination() { override val type get() = Type.MORE - override val fragment get() = MoreFragment.newInstance() } - object Message : Destination { - + @Serializable + object Message : Destination() { override val type get() = Type.MESSAGE - override val fragment get() = MessageFragment.newInstance() } -} +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt index bb8a8df36..ff968654d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt @@ -25,8 +25,8 @@ private fun generateTimetable(subject: String, room: String, roomOld: String) = diaryId = 0, date = LocalDate.now().minusDays(Random.nextLong(0, 8)), number = 1, - start = Instant.now(), - end = Instant.now().plus(Duration.ofHours(1)), + start = Instant.now().plus(Duration.ofHours(1)), + end = Instant.now(), subjectOld = "", group = "", room = room, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt index 27b3637ad..92c54f45c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt @@ -7,14 +7,13 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.data.db.entities.Notification import io.github.wulkanowy.databinding.ItemNotificationsCenterBinding -import io.github.wulkanowy.services.sync.notifications.NotificationType import io.github.wulkanowy.utils.toFormattedString import javax.inject.Inject class NotificationsCenterAdapter @Inject constructor() : ListAdapter(DiffUtilCallback()) { - var onItemClickListener: (NotificationType) -> Unit = {} + var onItemClickListener: (Notification) -> Unit = {} override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( ItemNotificationsCenterBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -29,7 +28,7 @@ class NotificationsCenterAdapter @Inject constructor() : notificationsCenterItemDate.text = item.date.toFormattedString("HH:mm, d MMM") notificationsCenterItemIcon.setImageResource(item.type.icon) - root.setOnClickListener { onItemClickListener(item.type) } + root.setOnClickListener { onItemClickListener(item) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt index f3bbc42de..4f1943f48 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt @@ -3,26 +3,14 @@ package io.github.wulkanowy.ui.modules.notificationscenter import android.os.Bundle import android.view.View import androidx.core.view.isVisible -import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Notification import io.github.wulkanowy.databinding.FragmentNotificationsCenterBinding -import io.github.wulkanowy.services.sync.notifications.NotificationType import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment -import io.github.wulkanowy.ui.modules.conference.ConferenceFragment -import io.github.wulkanowy.ui.modules.exam.ExamFragment -import io.github.wulkanowy.ui.modules.grade.GradeFragment -import io.github.wulkanowy.ui.modules.homework.HomeworkFragment -import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.ui.modules.message.MessageFragment -import io.github.wulkanowy.ui.modules.note.NoteFragment -import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment -import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import javax.inject.Inject @AndroidEntryPoint @@ -54,9 +42,8 @@ class NotificationsCenterFragment : } override fun initView() { - notificationsCenterAdapter.onItemClickListener = { notificationType -> - notificationType.toDestinationFragment() - ?.let { (requireActivity() as MainActivity).pushView(it) } + notificationsCenterAdapter.onItemClickListener = { notification -> + (requireActivity() as MainActivity).pushView(notification.destination.fragment) } with(binding.notificationsCenterRecycler) { @@ -93,20 +80,4 @@ class NotificationsCenterFragment : presenter.onDetachView() super.onDestroyView() } - - private fun NotificationType.toDestinationFragment(): Fragment? = when (this) { - NotificationType.NEW_CONFERENCE -> ConferenceFragment.newInstance() - NotificationType.NEW_EXAM -> ExamFragment.newInstance() - NotificationType.NEW_GRADE_DETAILS -> GradeFragment.newInstance() - NotificationType.NEW_GRADE_PREDICTED -> GradeFragment.newInstance() - NotificationType.NEW_GRADE_FINAL -> GradeFragment.newInstance() - NotificationType.NEW_HOMEWORK -> HomeworkFragment.newInstance() - NotificationType.NEW_LUCKY_NUMBER -> LuckyNumberFragment.newInstance() - NotificationType.NEW_MESSAGE -> MessageFragment.newInstance() - NotificationType.NEW_NOTE -> NoteFragment.newInstance() - NotificationType.NEW_ANNOUNCEMENT -> SchoolAnnouncementFragment.newInstance() - NotificationType.PUSH -> null - NotificationType.CHANGE_TIMETABLE -> TimetableFragment.newInstance() - NotificationType.NEW_ATTENDANCE -> AttendanceFragment.newInstance() - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterPresenter.kt index 394c23101..de42e5678 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterPresenter.kt @@ -48,7 +48,7 @@ class NotificationsCenterPresenter @Inject constructor( emitAll(notificationRepository.getNotifications(studentId)) } .map { notificationList -> notificationList.sortedByDescending { it.date } } - .catch { Timber.i("Loading notifications result: An exception occurred") } + .catch { Timber.i("Loading notifications result: An exception occurred: `$it`") } .onEach { Timber.i("Loading notifications result: Success") 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 d9a3780ba..a614c65dc 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 @@ -7,6 +7,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Notification import io.github.wulkanowy.data.repositories.NotificationRepository import io.github.wulkanowy.services.sync.notifications.NotificationType +import io.github.wulkanowy.ui.modules.Destination import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -38,6 +39,7 @@ class AppMessagingService : FirebaseMessagingService() { data = customData, date = Instant.now(), type = NotificationType.PUSH, + destination = Destination.Dashboard, studentId = -1 ) From d07b0dbc986aeed39718092fe77f335637ad54c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jan 2022 04:46:09 +0000 Subject: [PATCH 048/105] Bump WhatTheStack from 1.0.0-alpha02 to 1.0.0-alpha03 (#1768) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 498673416..e4c4034b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -246,7 +246,7 @@ dependencies { debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6' - debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha02' + debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha03' testImplementation "junit:junit:4.13.2" testImplementation "io.mockk:mockk:$mockk" From 0b0993be9aaa41616427df28804e827b66056647 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jan 2022 04:47:00 +0000 Subject: [PATCH 049/105] Bump agcp from 1.6.3.300 to 1.6.4.200 (#1772) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6c0e709d2..42dcc6921 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.3.300' + classpath 'com.huawei.agconnect:agcp:1.6.4.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.1" From cfcc051ce40a00c22758a12affa9f432147a96b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jan 2022 04:47:26 +0000 Subject: [PATCH 050/105] Bump gradle from 7.0.4 to 7.1.0 (#1769) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 42dcc6921..0da00b390 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath 'com.android.tools.build:gradle:7.0.4' + classpath 'com.android.tools.build:gradle:7.1.0' 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.4.200' From 01b8bd9d4a8f7fc7c030ab0ab8d636288ba466f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jan 2022 04:47:56 +0000 Subject: [PATCH 051/105] Bump agconnect-crash from 1.6.3.300 to 1.6.4.200 (#1770) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e4c4034b3..3f7b95151 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -240,7 +240,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.3.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.4.200' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From a4f455b38fd06d85096e1cb69db49e871a199710 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jan 2022 04:55:02 +0000 Subject: [PATCH 052/105] Bump hianalytics from 6.3.2.300 to 6.4.0.300 (#1771) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3f7b95151..1a0e00ae5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -239,7 +239,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:20.5.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.3.2.300' + hmsImplementation 'com.huawei.hms:hianalytics:6.4.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.4.200' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From ce36e86bb2b1bfcd488ebefb541150932c53072c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jan 2022 05:38:55 +0000 Subject: [PATCH 053/105] Bump preference-ktx from 1.1.1 to 1.2.0 (#1773) --- app/build.gradle | 2 +- .../github/wulkanowy/ui/modules/main/MainActivity.kt | 6 ++++-- .../ui/modules/settings/advanced/AdvancedFragment.kt | 4 ++-- .../modules/settings/appearance/AppearanceFragment.kt | 4 ++-- .../settings/notifications/NotificationsFragment.kt | 10 +++++----- .../wulkanowy/ui/modules/settings/sync/SyncFragment.kt | 4 ++-- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1a0e00ae5..a31b6b1fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { implementation "androidx.fragment:fragment-ktx:1.4.0" implementation "androidx.annotation:annotation:1.3.0" - implementation "androidx.preference:preference-ktx:1.1.1" + implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 02af02adc..0cd38ac7d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -173,8 +173,10 @@ class MainActivity : BaseActivity(), MainVie caller: PreferenceFragmentCompat, pref: Preference ): Boolean { - val fragment = - supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment) + val fragment = supportFragmentManager.fragmentFactory.instantiate( + classLoader, + pref.fragment.toString() + ) pushView(fragment) return true } 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 bef726cad..b4ba5bc4b 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 @@ -64,11 +64,11 @@ class AdvancedFragment : PreferenceFragmentCompat(), override fun onResume() { super.onResume() - preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) } override fun onPause() { super.onPause() - preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt index f603de781..1f6d5143b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -80,11 +80,11 @@ class AppearanceFragment : PreferenceFragmentCompat(), override fun onResume() { super.onResume() - preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) } override fun onPause() { super.onPause() - preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index 84fee7179..364ad2137 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -83,10 +83,10 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun onCreateRecyclerView( - inflater: LayoutInflater?, - parent: ViewGroup?, + inflater: LayoutInflater, + parent: ViewGroup, state: Bundle? - ): RecyclerView? = super.onCreateRecyclerView(inflater, parent, state) + ): RecyclerView = super.onCreateRecyclerView(inflater, parent, state) .also { it.itemAnimator = null it.layoutAnimation = null @@ -214,11 +214,11 @@ class NotificationsFragment : PreferenceFragmentCompat(), override fun onResume() { super.onResume() - preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) } override fun onPause() { super.onPause() - preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) } } 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 d81c35d39..8477e3222 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 @@ -96,11 +96,11 @@ class SyncFragment : PreferenceFragmentCompat(), override fun onResume() { super.onResume() - preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) } override fun onPause() { super.onPause() - preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) } } From 923af85d18bdec1b3dd14173e131687d1242c3b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 01:53:16 +0000 Subject: [PATCH 054/105] Bump fragment-ktx from 1.4.0 to 1.4.1 (#1774) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a31b6b1fb..92f76303c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,7 +187,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.0-beta01' implementation "androidx.activity:activity-ktx:1.4.0" implementation "androidx.appcompat:appcompat:1.4.1" - implementation "androidx.fragment:fragment-ktx:1.4.0" + implementation "androidx.fragment:fragment-ktx:1.4.1" implementation "androidx.annotation:annotation:1.3.0" implementation "androidx.preference:preference-ktx:1.2.0" From 96ee4bd9e5d5d516f1af9a90c2884c10fcdde912 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Wed, 2 Feb 2022 03:44:14 +0100 Subject: [PATCH 055/105] Keep reacting to live changes in dashboard after a force refresh (#1594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Borcz --- .../modules/dashboard/DashboardPresenter.kt | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) 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 a1845ab59..cad8d112b 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 @@ -104,7 +104,7 @@ class DashboardPresenter @Inject constructor( forceRefresh: Boolean ) = dashboardTilesToLoad.filter { newItemToLoad -> dashboardLoadedTiles.none { it == newItemToLoad } || forceRefresh - || newItemToLoad == DashboardItem.Tile.ADMIN_MESSAGE + || newItemToLoad == DashboardItem.Tile.ADMIN_MESSAGE } private fun removeUnselectedTiles(tilesToLoad: List) { @@ -254,7 +254,8 @@ class DashboardPresenter @Inject constructor( attendanceFlow ) { luckyNumberResource, messageResource, attendanceResource -> val error = - luckyNumberResource?.error ?: messageResource?.error ?: attendanceResource?.error + luckyNumberResource?.error ?: messageResource?.error + ?: attendanceResource?.error error?.let { throw it } val luckyNumber = luckyNumberResource?.data?.luckyNumber @@ -295,7 +296,7 @@ class DashboardPresenter @Inject constructor( ) errorHandler.dispatch(it) } - .launch("horizontal_group") + .launch("horizontal_group ${if (forceRefresh) "-forceRefresh" else ""}") } private fun loadGrades(student: Student, forceRefresh: Boolean) { @@ -356,7 +357,7 @@ class DashboardPresenter @Inject constructor( updateData(DashboardItem.Grades(error = it.error), forceRefresh) } } - }.launch("dashboard_grades") + }.launchWithUniqueRefreshJob("dashboard_grades", forceRefresh) } private fun loadLessons(student: Student, forceRefresh: Boolean) { @@ -400,7 +401,7 @@ class DashboardPresenter @Inject constructor( ) } } - }.launch("dashboard_lessons") + }.launchWithUniqueRefreshJob("dashboard_lessons", forceRefresh) } private fun loadHomework(student: Student, forceRefresh: Boolean) { @@ -447,7 +448,7 @@ class DashboardPresenter @Inject constructor( updateData(DashboardItem.Homework(error = it.error), forceRefresh) } } - }.launch("dashboard_homework") + }.launchWithUniqueRefreshJob("dashboard_homework", forceRefresh) } private fun loadSchoolAnnouncements(student: Student, forceRefresh: Boolean) { @@ -477,7 +478,7 @@ class DashboardPresenter @Inject constructor( updateData(DashboardItem.Announcements(error = it.error), forceRefresh) } } - }.launch("dashboard_announcements") + }.launchWithUniqueRefreshJob("dashboard_announcements", forceRefresh) } private fun loadExams(student: Student, forceRefresh: Boolean) { @@ -521,7 +522,7 @@ class DashboardPresenter @Inject constructor( updateData(DashboardItem.Exams(error = it.error), forceRefresh) } } - }.launch("dashboard_exams") + }.launchWithUniqueRefreshJob("dashboard_exams", forceRefresh) } private fun loadConferences(student: Student, forceRefresh: Boolean) { @@ -558,7 +559,7 @@ class DashboardPresenter @Inject constructor( updateData(DashboardItem.Conferences(error = it.error), forceRefresh) } } - }.launch("dashboard_conferences") + }.launchWithUniqueRefreshJob("dashboard_conferences", forceRefresh) } private fun loadAdminMessage(student: Student, forceRefresh: Boolean) { @@ -594,7 +595,7 @@ class DashboardPresenter @Inject constructor( } } } - .launch("dashboard_admin_messages") + .launchWithUniqueRefreshJob("dashboard_admin_messages", forceRefresh) } private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) { @@ -733,4 +734,18 @@ class DashboardPresenter @Inject constructor( dashboardItemsPosition?.getOrDefault(tile.type, defaultPosition) ?: tile.type.ordinal } } -} \ No newline at end of file + + private fun Flow>.launchWithUniqueRefreshJob(name: String, forceRefresh: Boolean) { + val jobName = if (forceRefresh) "$name-forceRefresh" else name + + if (forceRefresh) { + onEach { + if (it.status == Status.SUCCESS) { + cancelJobs(jobName) + } + }.launch(jobName) + } else { + launch(jobName) + } + } +} From be046a1ddd879dfe7a7eacb46a93011c6bf72878 Mon Sep 17 00:00:00 2001 From: Patryk <43276401+Zaptyp@users.noreply.github.com> Date: Mon, 7 Feb 2022 03:16:17 +0100 Subject: [PATCH 056/105] Update date in LICENSE file (#1775) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 2fb96cee8..c97032f74 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2021 Wulkanowy + Copyright 2022 Wulkanowy Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 6290663f02834f5866e1108bce745ef45b1aca69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 04:41:09 +0000 Subject: [PATCH 057/105] Bump gradle from 7.1.0 to 7.1.1 (#1777) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0da00b390..ab598e633 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath 'com.android.tools.build:gradle:7.1.0' + classpath 'com.android.tools.build:gradle:7.1.1' 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.4.200' From 84d0ba525f440f7310aacb09e8f551716ee97c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 10 Feb 2022 07:36:44 +0100 Subject: [PATCH 058/105] Fix blank description in exam details (#1778) --- .../io/github/wulkanowy/ui/modules/exam/ExamDialog.kt | 5 ++++- .../ui/modules/grade/details/GradeDetailsDialog.kt | 10 ++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt index 3f815a2cb..3de9874da 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment +import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.databinding.DialogExamBinding import io.github.wulkanowy.utils.lifecycleAwareVariable @@ -47,7 +48,9 @@ class ExamDialog : DialogFragment() { examDialogTypeValue.text = exam.type examDialogTeacherValue.text = exam.teacher examDialogDateValue.text = exam.entryDate.toFormattedString() - examDialogDescriptionValue.text = exam.description + examDialogDescriptionValue.text = exam.description.ifBlank { + getString(R.string.all_no_data) + } examDialogClose.setOnClickListener { dismiss() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt index a9d9039dd..3c747b949 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt @@ -10,11 +10,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.databinding.DialogGradeBinding -import io.github.wulkanowy.utils.colorStringId -import io.github.wulkanowy.utils.getBackgroundColor -import io.github.wulkanowy.utils.getGradeColor -import io.github.wulkanowy.utils.lifecycleAwareVariable -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* class GradeDetailsDialog : DialogFragment() { @@ -80,9 +76,7 @@ class GradeDetailsDialog : DialogFragment() { setBackgroundResource(grade.getBackgroundColor(gradeColorTheme)) } - gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) { - getString(R.string.all_no_data) - } else grade.teacher + gradeDialogTeacherValue.text = grade.teacher.ifBlank { getString(R.string.all_no_data) } gradeDialogDescriptionValue.text = grade.run { when { From 18568c86beec256ca63cbd85636cad592ff43d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 12 Feb 2022 12:19:25 +0100 Subject: [PATCH 059/105] New Crowdin updates (#1776) --- app/src/main/res/values-ru/strings.xml | 36 +++++++++++++------------- app/src/main/res/values-uk/strings.xml | 36 +++++++++++++------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b5ad3683e..2ae1aad7f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -204,17 +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 + Новый урок + Новый дополнительный урок + Дополнительный урок успешно добавлен + Дополнительный урок успешно удален + Повторять еженедельно + Удалить дополнительный урок + Просто этот урок + Все в серии + Время начала + Время окончания + Время окончания должно быть больше, чем время начала Итоговая посещаемость Отсутствие по школьным причинам @@ -264,10 +264,10 @@ Новые экзамены - %d new exam - %d new exams - %d new exams - %d new exams + %d новый экзамен + %d новый экзамен + %d новый экзамен + %d новых экзаменов %d экзамен @@ -679,9 +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ТОЛЬКО ДЛЯ ПОЛЬЗОВАТЕЛЯ Показывать уведомления о будущих уроках @@ -760,7 +760,7 @@ Не удалось обновить! Wulkanowy может работать некорректно. Рассмотрите возможность обновления Нет интернет-подключения - An error occurred. Check your device clock + Произошла ошибка. Проверьте часы вашего устройства Не удалось подключиться к регистрации. Серверы могут быть перегружены. Пожалуйста, повторите попытку позже Не удалось загрузить данные. Пожалуйста, повторите попытку позже Необходимо изменить пароль реестра diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 0c88df317..9dedb2060 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -204,17 +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 + Новий урок + Новий додатковий урок + Додатковий урок успішно додано + Успішно видалено додаткове заняття + Повторювати щотижня + Видалити додатковий урок + Тільки цей урок + Все в серії + Час початку + Час завершення + Час завершення має бути більшим, ніж час початку Підсумок відвідуваності Відсутність зі шкільних причин @@ -264,10 +264,10 @@ Нові іспити - %d new exam - %d new exams - %d new exams - %d new exams + %d новий екзамен + %d новий екзамен + %d новий екзамен + %d нових іспитів %d екзамен @@ -679,9 +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 ТІЛЬКИ ДЛЯ РОЗШИРЕНИХ КОРИСТУВАЧІВ Показувати повідомлення о наступних уроках @@ -760,7 +760,7 @@ Помилка оновлення! Wulkanowy може не працювати належним чином. Подумайте про оновлення Брак з\'єднання з інтернетом - An error occurred. Check your device clock + Сталася помилка. Перевірте годинник пристрою Помилка підключення до реєстрації. Сервери можуть бути перевантажені. Будь-ласка спробуйте пізніше Помилка завантаження даних. Будь-ласка спробуйте пізніше Потрібна реєстрація зміни пароля From edd1c9442e080ea9367a66abef8a84b98a242a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 12 Feb 2022 22:22:15 +0100 Subject: [PATCH 060/105] Fix calc vulcan average in second semester (#1779) --- .../ui/modules/grade/GradeAverageProvider.kt | 26 +++++------ .../modules/grade/GradeAverageProviderTest.kt | 45 +++++++++++++++++-- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 2784f429f..0c18ce95e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -10,9 +10,7 @@ import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR -import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS -import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.* import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier import io.github.wulkanowy.utils.flowWithResourceIn @@ -144,20 +142,20 @@ class GradeAverageProvider @Inject constructor( isGradeAverageForceCalc: Boolean, secondSemesterSubject: GradeSubject, firstSemesterSubject: GradeSubject? - ): Double { + ): Double = if (!isAnyVulcanAverage || isGradeAverageForceCalc) { val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1 - return if (!isAnyVulcanAverage || isGradeAverageForceCalc) { - val secondSemesterAverage = - secondSemesterSubject.grades.updateModifiers(student) - .calcAverage(isOptionalArithmeticAverage) - val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student) - ?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage + val secondSemesterAverage = secondSemesterSubject.grades.updateModifiers(student) + .calcAverage(isOptionalArithmeticAverage) + val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student) + ?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage - (secondSemesterAverage + firstSemesterAverage) / divider - } else { - (secondSemesterSubject.average + (firstSemesterSubject?.average ?: secondSemesterSubject.average)) / divider - } + (secondSemesterAverage + firstSemesterAverage) / divider + } else { + val divider = if (secondSemesterSubject.average > 0) 2 else 1 + + (secondSemesterSubject.average + (firstSemesterSubject?.average + ?: secondSemesterSubject.average)) / divider } private fun getGradeSubjects( 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 f097cb845..5e8c4c119 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 @@ -859,12 +859,51 @@ class GradeAverageProviderTest { ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } - assertEquals(5.5555, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,8 → .average() + assertEquals( + 5.5555, + items.single { it.subject == "Fizyka" }.average, + .0001 + ) // (from details): 5.72727272 + 4,8 → .average() } - private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0, weight: Double = 1.0, entry: String = ""): Grade { + @Test + fun `calc both semesters average when both summary have same average from vulcan and second semester has no grades`() { + every { preferencesRepository.gradeAverageForceCalc } returns false + every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS + every { preferencesRepository.isOptionalArithmeticAverage } returns false + + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns + flowWithResource { firstGrades to firstSummaries } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns + flowWithResource { listOf() to firstSummaries } + + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } + + assertEquals(3.1, items.single { it.subject == "Fizyka" }.average, .0001) + } + + private fun getGrade( + semesterId: Int, + subject: String, + value: Double, + modifier: Double = 0.0, + weight: Double = 1.0, + entry: String = "" + ): Grade { return Grade( studentId = 101, semesterId = semesterId, From dec2703cc7a36a59eec6b90360e4245644502b66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 20:05:45 +0000 Subject: [PATCH 061/105] Bump firebase-bom from 29.0.4 to 29.1.0 (#1782) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 92f76303c..5bfc0b8ea 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -231,7 +231,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.6.0' - playImplementation platform('com.google.firebase:firebase-bom:29.0.4') + playImplementation platform('com.google.firebase:firebase-bom:29.1.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From d3bf5c3e0ac32e8dfaf856476d1249d44fa6249a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 20:06:05 +0000 Subject: [PATCH 062/105] Bump lifecycle-livedata-ktx from 2.4.0 to 2.4.1 (#1781) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5bfc0b8ea..e9029a23b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -204,7 +204,7 @@ dependencies { implementation "androidx.work:work-runtime-ktx:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1" implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-ktx:$room" From aff0fb3a6028bafeac7c91fb22bf9a3872f13840 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Tue, 15 Feb 2022 13:08:44 +0100 Subject: [PATCH 063/105] Add information about student in grade statistics pie chart (#1749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- app/build.gradle | 2 + .../db/entities/GradeSemesterStatistics.kt | 5 +- .../repositories/GradeStatisticsRepository.kt | 59 +++++++------ .../statistics/GradeStatisticsAdapter.kt | 45 +++++++--- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values/strings.xml | 4 +- .../GradeStatisticsRepositoryTest.kt | 85 +++++++++++++++---- 12 files changed, 153 insertions(+), 59 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e9029a23b..609a54eb9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,6 +73,8 @@ android { buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" } debug { + minifyEnabled false + shrinkResources false resValue "string", "app_name", "Wulkanowy DEV" applicationIdSuffix ".dev" versionNameSuffix "-dev" diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt index e747271ce..9e08b86bc 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt @@ -24,5 +24,8 @@ data class GradeSemesterStatistics( var id: Long = 0 @Transient - var average: String = "" + var classAverage: String = "" + + @Transient + var studentAverage: String = "" } 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 356c203d0..4d26c3126 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 @@ -63,20 +63,16 @@ class GradeStatisticsRepository @Inject constructor( mapResult = { items -> when (subjectName) { "Wszystkie" -> { - val numerator = items.map { - it.classAverage.replace(",", ".").toDoubleOrNull() ?: .0 - }.filterNot { it == .0 } - (items.reversed() + GradePartialStatistics( + val summaryItem = GradePartialStatistics( studentId = semester.studentId, semesterId = semester.semesterId, subject = subjectName, - classAverage = if (numerator.isEmpty()) "" else numerator.average().let { - "%.2f".format(Locale.FRANCE, it) - }, - studentAverage = "", + classAverage = items.map { it.classAverage }.getSummaryAverage(), + studentAverage = items.map { it.studentAverage }.getSummaryAverage(), classAmounts = items.map { it.classAmounts }.sumGradeAmounts(), studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts() - )).reversed() + ) + listOf(summaryItem) + items } else -> items.filter { it.subject == subjectName } }.mapPartialToStatisticItems() @@ -112,29 +108,29 @@ class GradeStatisticsRepository @Inject constructor( val itemsWithAverage = items.map { item -> item.copy().apply { val denominator = item.amounts.sum() - average = if (denominator == 0) "" else { + classAverage = if (denominator == 0) "" else { (item.amounts.mapIndexed { gradeValue, amount -> (gradeValue + 1) * amount - }.sum().toDouble() / denominator).let { - "%.2f".format(Locale.FRANCE, it) - } + }.sum().toDouble() / denominator).asAverageString() } } } when (subjectName) { - "Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics( - studentId = semester.studentId, - semesterId = semester.semesterId, - subject = subjectName, - amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(), - studentGrade = 0 - ).apply { - average = itemsWithAverage.mapNotNull { - it.average.replace(",", ".").toDoubleOrNull() - }.average().let { - "%.2f".format(Locale.FRANCE, it) + "Wszystkie" -> { + val summaryItem = GradeSemesterStatistics( + studentId = semester.studentId, + semesterId = semester.semesterId, + subject = subjectName, + amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(), + studentGrade = 0, + ).apply { + classAverage = itemsWithAverage.map { it.classAverage }.getSummaryAverage() + studentAverage = items + .mapNotNull { summary -> summary.studentGrade.takeIf { it != 0 } } + .average().asAverageString() } - }).reversed() + listOf(summaryItem) + itemsWithAverage + } else -> itemsWithAverage.filter { it.subject == subjectName } }.mapSemesterToStatisticItems() } @@ -171,6 +167,19 @@ class GradeStatisticsRepository @Inject constructor( } ) + private fun List.getSummaryAverage(): String { + val averages = mapNotNull { + it.replace(",", ".").toDoubleOrNull() + } + + return averages.average() + .asAverageString() + .takeIf { averages.isNotEmpty() } + .orEmpty() + } + + private fun Double.asAverageString(): String = "%.2f".format(Locale.FRANCE, this) + private fun List>.sumGradeAmounts(): List { val result = mutableListOf(0, 0, 0, 0, 0, 0) forEach { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt index 6be2d969f..fd0ac5471 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt @@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.github.mikephil.charting.components.Legend import com.github.mikephil.charting.components.LegendEntry -import com.github.mikephil.charting.data.BarData -import com.github.mikephil.charting.data.BarDataSet -import com.github.mikephil.charting.data.BarEntry -import com.github.mikephil.charting.data.PieData -import com.github.mikephil.charting.data.PieDataSet -import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.data.* import com.github.mikephil.charting.formatter.ValueFormatter import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.GradePartialStatistics @@ -136,20 +131,50 @@ class GradeStatisticsAdapter @Inject constructor() : binding: ItemGradeStatisticsPieBinding, partials: GradePartialStatistics ) { - bindPieChart(binding, partials.subject, partials.classAverage, partials.classAmounts) + val studentAverage = partials.studentAverage.takeIf { it.isNotEmpty() }?.let { + binding.root.context.getString(R.string.grade_statistics_student_average, it) + } + bindPieChart( + binding = binding, + subject = partials.subject, + average = partials.classAverage, + studentValue = studentAverage, + amounts = partials.classAmounts + ) } private fun bindSemesterChart( binding: ItemGradeStatisticsPieBinding, semester: GradeSemesterStatistics ) { - bindPieChart(binding, semester.subject, semester.average, semester.amounts) + val studentAverage = semester.studentAverage.takeIf { it.isNotBlank() } + val studentGrade = semester.studentGrade.takeIf { it != 0 } + + val studentValue = when { + studentAverage != null -> binding.root.context.getString( + R.string.grade_statistics_student_average, + studentAverage + ) + studentGrade != null -> binding.root.context.getString( + R.string.grade_statistics_student_grade, + studentGrade.toString() + ) + else -> null + } + bindPieChart( + binding = binding, + subject = semester.subject, + average = semester.classAverage, + studentValue = studentValue, + amounts = semester.amounts + ) } private fun bindPieChart( binding: ItemGradeStatisticsPieBinding, subject: String, average: String, + studentValue: String?, amounts: List ) { with(binding.gradeStatisticsPieTitle) { @@ -208,13 +233,13 @@ class GradeStatisticsAdapter @Inject constructor() : val numberOfGradesString = amounts.fold(0) { acc, it -> acc + it } .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } val averageString = - binding.root.context.getString(R.string.grade_statistics_average, average) + binding.root.context.getString(R.string.grade_statistics_class_average, average) minAngleForSlices = 25f description.isEnabled = false centerText = numberOfGradesString + ("\n\n" + averageString).takeIf { average.isNotBlank() } - .orEmpty() + .orEmpty() + studentValue?.let { "\n$it" }.orEmpty() setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground)) setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary)) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index b56617ed7..e7c2da332 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -105,7 +105,7 @@ Semestr Body Vysvětlivky - Průměr: %1$s + Průměr: %1$s Třída Žák diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5b2fee80d..68c371d51 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -105,7 +105,7 @@ Semester Punkte Legende - Durchschnitt: %1$s + Durchschnitt: %1$s Klasse Schüler diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 208e6f3ea..da127fe2d 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -105,7 +105,7 @@ Semestralne Punkty Legenda - Średnia: %1$s + Średnia: %1$s Klasa Uczeń diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2ae1aad7f..e6e9834f5 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -105,7 +105,7 @@ За семестр Баллы Легенда - Средняя: %1$s + Средняя: %1$s Класс Студент diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 30365fe56..ba701eafd 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -105,7 +105,7 @@ Semester Body Vysvetlivky - Priemer: %1$s + Priemer: %1$s Trieda Žiák diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 9dedb2060..7bcffe01b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -105,7 +105,7 @@ Семестрові Бали Умовні позначення - Середня оцінка: %1$s + Середня оцінка: %1$s Клас Учень diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9c63073e2..d565d65cc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -115,7 +115,9 @@ Semester Points Legend - Average: %1$s + Class average: %1$s + Your average: %1$s + Your grade: %1$s Class Student diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt index cce3794de..6221b6989 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt @@ -11,14 +11,9 @@ import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject 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 @@ -48,22 +43,27 @@ class GradeStatisticsRepositoryTest { private lateinit var gradeStatisticsRepository: GradeStatisticsRepository - private val remotePartialList = listOf( - getGradeStatisticsPartialSubject("Fizyka"), - getGradeStatisticsPartialSubject("Matematyka") - ) - @Before fun setUp() { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - gradeStatisticsRepository = GradeStatisticsRepository(gradePartialStatisticsDb, gradePointsStatisticsDb, gradeSemesterStatisticsDb, sdk, refreshHelper) + gradeStatisticsRepository = GradeStatisticsRepository( + gradePartialStatisticsDb = gradePartialStatisticsDb, + gradePointsStatisticsDb = gradePointsStatisticsDb, + gradeSemesterStatisticsDb = gradeSemesterStatisticsDb, + sdk = sdk, + refreshHelper = refreshHelper, + ) } @Test fun `force refresh without difference`() { // prepare + val remotePartialList = listOf( + getGradeStatisticsPartialSubject("Fizyka"), + getGradeStatisticsPartialSubject("Matematyka") + ) coEvery { sdk.getGradesPartialStatistics(1) } returns remotePartialList coEvery { gradePartialStatisticsDb.loadAll(1, 1) } returnsMany listOf( flowOf(remotePartialList.mapToEntities(semester)), @@ -73,21 +73,74 @@ class GradeStatisticsRepositoryTest { coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs // execute - val res = runBlocking { gradeStatisticsRepository.getGradesPartialStatistics(student, semester, "Wszystkie", true).toFirstResult() } + val res = runBlocking { + gradeStatisticsRepository.getGradesPartialStatistics( + student = student, + semester = semester, + subjectName = "Wszystkie", + forceRefresh = true, + ).toFirstResult() + } + val items = res.data.orEmpty() // verify assertEquals(null, res.error) assertEquals(2 + 1, res.data?.size) + assertEquals("", items[0].partial?.studentAverage) + assertEquals("", items[1].partial?.studentAverage) + assertEquals("", items[2].partial?.studentAverage) coVerify { sdk.getGradesPartialStatistics(1) } coVerify { gradePartialStatisticsDb.loadAll(1, 1) } coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) } coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) } } - private fun getGradeStatisticsPartialSubject(subjectName: String) = GradeStatisticsSubject( + @Test + fun `force refresh without difference with filled up items`() { + // prepare + val remotePartialList = listOf( + getGradeStatisticsPartialSubject("Fizyka", "1.0"), + getGradeStatisticsPartialSubject("Matematyka", "5.0") + ) + coEvery { sdk.getGradesPartialStatistics(1) } returns remotePartialList + coEvery { gradePartialStatisticsDb.loadAll(1, 1) } returnsMany listOf( + flowOf(remotePartialList.mapToEntities(semester)), + flowOf(remotePartialList.mapToEntities(semester)) + ) + coEvery { gradePartialStatisticsDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { + gradeStatisticsRepository.getGradesPartialStatistics( + student = student, + semester = semester, + subjectName = "Wszystkie", + forceRefresh = true, + ).toFirstResult() + } + val items = res.data.orEmpty() + + // verify + assertEquals(null, res.error) + assertEquals(2 + 1, res.data?.size) + assertEquals("3,00", items[0].partial?.studentAverage) + assertEquals("1.0", items[1].partial?.studentAverage) + assertEquals("5.0", items[2].partial?.studentAverage) + coVerify { sdk.getGradesPartialStatistics(1) } + coVerify { gradePartialStatisticsDb.loadAll(1, 1) } + coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) } + coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) } + } + + private fun getGradeStatisticsPartialSubject( + subjectName: String, + studentAverage: String = "", + classAverage: String = "", + ) = GradeStatisticsSubject( subject = subjectName, - studentAverage = "", - classAverage = "", + studentAverage = studentAverage, + classAverage = classAverage, classItems = listOf( GradeStatisticsItem( subject = subjectName, From 820b99dbc7ab78132a5abd99833c9d1d1f36eea5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 19:35:02 +0000 Subject: [PATCH 064/105] Bump hilt_version from 2.40.5 to 2.41 (#1786) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ab598e633..0571396e3 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.6.10' about_libraries = '8.9.4' - hilt_version = "2.40.5" + hilt_version = "2.41" } repositories { mavenCentral() From e7561d4794af3c51f1e3fb9ecdffdb5b4885cbcd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 18:32:08 +0000 Subject: [PATCH 065/105] Bump room from 2.4.1 to 2.4.2 (#1794) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 609a54eb9..ca5f802f4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -171,7 +171,7 @@ huaweiPublish { ext { work_manager = "2.7.1" android_hilt = "1.0.0" - room = "2.4.1" + room = "2.4.2" chucker = "3.5.2" mockk = "1.12.2" coroutines = "1.6.0" From 8915c5dd8ecc9622bd3efe2ae8b027997e1819a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 18:32:31 +0000 Subject: [PATCH 066/105] Bump agconnect-crash from 1.6.4.200 to 1.6.4.300 (#1793) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ca5f802f4..3b73a92f7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:20.5.0' hmsImplementation 'com.huawei.hms:hianalytics:6.4.0.300' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.4.200' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.4.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 9a413c14c329ebb0e0f741c566e2f2555a864bb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 18:37:29 +0000 Subject: [PATCH 067/105] Bump agcp from 1.6.4.200 to 1.6.4.300 (#1791) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0571396e3..1b7e01bf7 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.1.1' 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.4.200' + classpath 'com.huawei.agconnect:agcp:1.6.4.300' 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.1" From f48caf9f70afdca48e22d3104b4dc3235d1c66c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 18:41:16 +0000 Subject: [PATCH 068/105] Bump play-services-ads from 20.5.0 to 20.6.0 (#1792) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3b73a92f7..960096acc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -239,7 +239,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.android.play:core:1.10.3' playImplementation 'com.google.android.play:core-ktx:1.8.1' - playImplementation 'com.google.android.gms:play-services-ads:20.5.0' + playImplementation 'com.google.android.gms:play-services-ads:20.6.0' hmsImplementation 'com.huawei.hms:hianalytics:6.4.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.4.300' From c3abe50ed4d074426af3027d39c5e2195fa881db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 18:59:53 +0000 Subject: [PATCH 069/105] Bump gradle from 7.1.1 to 7.1.2 (#1790) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1b7e01bf7..1a97f58ce 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath 'com.android.tools.build:gradle:7.1.1' + classpath 'com.android.tools.build:gradle:7.1.2' 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.4.300' From 57ea6379aba1b5271aff1e72c0d5482d3848f682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 13 Mar 2022 04:01:14 +0100 Subject: [PATCH 070/105] Timetable timer refactor (#1785) --- .editorconfig | 12 + .../ui/modules/timetable/TimetableAdapter.kt | 226 ++++++------------ .../ui/modules/timetable/TimetableFragment.kt | 24 +- .../ui/modules/timetable/TimetableItem.kt | 30 +++ .../modules/timetable/TimetablePresenter.kt | 96 +++++--- .../ui/modules/timetable/TimetableView.kt | 8 +- 6 files changed, 190 insertions(+), 206 deletions(-) create mode 100644 .editorconfig create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..35fbd4661 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +indent_style=space +indent_size=4 + +[*.json] +indent_size=2 + +[*.{kt,kts}] +disabled_rules=import-ordering,no-wildcard-imports 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 eacd12c69..d6917672a 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 @@ -1,116 +1,69 @@ package io.github.wulkanowy.ui.modules.timetable -import android.graphics.Paint -import android.os.Handler -import android.os.Looper import android.view.LayoutInflater import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.TextView +import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R 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.* -import timber.log.Timber -import java.time.Instant -import java.util.* +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.toFormattedString import javax.inject.Inject -import kotlin.concurrent.timer -class TimetableAdapter @Inject constructor() : RecyclerView.Adapter() { +class TimetableAdapter @Inject constructor() : + ListAdapter(differ) { - private enum class ViewType { - ITEM_NORMAL, - ITEM_SMALL - } - - var onClickListener: (Timetable) -> Unit = {} - - private var showWholeClassPlan = TimetableMode.ONLY_CURRENT_GROUP - - private var showGroupsInPlan: Boolean = false - - private var showTimers: Boolean = false - - private val timers = mutableMapOf() - - private val items = mutableListOf() - - fun submitList( - newTimetable: List, - showWholeClassPlan: TimetableMode = this.showWholeClassPlan, - showGroupsInPlan: Boolean = this.showGroupsInPlan, - showTimers: Boolean = this.showTimers - ) { - val isFlagsDifferent = this.showWholeClassPlan != showWholeClassPlan - || this.showGroupsInPlan != showGroupsInPlan - || this.showTimers != showTimers - - val diffResult = DiffUtil.calculateDiff( - TimetableAdapterDiffCallback( - oldList = items.toMutableList(), - newList = newTimetable, - isFlagsDifferent = isFlagsDifferent - ) - ) - - this.showGroupsInPlan = showGroupsInPlan - this.showTimers = showTimers - this.showWholeClassPlan = showWholeClassPlan - - items.clear() - items.addAll(newTimetable) - - diffResult.dispatchUpdatesTo(this) - } - - fun clearTimers() { - Timber.d("Timetable timers (${timers.size}) cleared") - with(timers) { - forEach { (_, timer) -> - timer?.cancel() - timer?.purge() - } - clear() - } - } - - override fun getItemCount() = items.size - - override fun getItemViewType(position: Int) = when { - !items[position].isStudentPlan && showWholeClassPlan == TimetableMode.SMALL_OTHER_GROUP -> ViewType.ITEM_SMALL.ordinal - else -> ViewType.ITEM_NORMAL.ordinal - } + override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) - return when (viewType) { - ViewType.ITEM_NORMAL.ordinal -> ItemViewHolder( - ItemTimetableBinding.inflate(inflater, parent, false) - ) - ViewType.ITEM_SMALL.ordinal -> SmallItemViewHolder( + return when (TimetableItemType.values()[viewType]) { + TimetableItemType.SMALL -> SmallViewHolder( ItemTimetableSmallBinding.inflate(inflater, parent, false) ) - else -> throw IllegalStateException() + TimetableItemType.NORMAL -> NormalViewHolder( + ItemTimetableBinding.inflate(inflater, parent, false) + ) } } + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int, + payloads: MutableList + ) { + if (payloads.isEmpty()) return super.onBindViewHolder(holder, position, payloads) + + if (holder is NormalViewHolder) updateTimeLeft( + binding = holder.binding, + timeLeft = (getItem(position) as TimetableItem.Normal).timeLeft, + ) + } + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - val lesson = items[position] - when (holder) { - is ItemViewHolder -> bindNormalView(holder.binding, lesson, position) - is SmallItemViewHolder -> bindSmallView(holder.binding, lesson) + is SmallViewHolder -> bindSmallView( + binding = holder.binding, + item = getItem(position) as TimetableItem.Small, + ) + is NormalViewHolder -> bindNormalView( + binding = holder.binding, + item = getItem(position) as TimetableItem.Normal, + ) } } - private fun bindSmallView(binding: ItemTimetableSmallBinding, lesson: Timetable) { + private fun bindSmallView(binding: ItemTimetableSmallBinding, item: TimetableItem.Small) { + val lesson = item.lesson + with(binding) { timetableSmallItemNumber.text = lesson.number.toString() timetableSmallItemSubject.text = lesson.subject @@ -122,11 +75,13 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter i < position && !item.isStudentPlan }.size) - ?.let { - if (!it.canceled && it.isStudentPlan) it.end - else null - } - } - - private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) { - val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(position)) - val until = lesson.until.plusMinutes(1) - val left = lesson.left?.plusMinutes(1) - val isJustFinished = lesson.isJustFinished - + private fun updateTimeLeft(binding: ItemTimetableBinding, timeLeft: TimeLeft?) { with(binding) { when { // before lesson - isShowTimeUntil -> { - Timber.d("Show time until lesson: $position") + timeLeft?.until != null -> { timetableItemTimeLeft.visibility = GONE with(timetableItemTimeUntil) { visibility = VISIBLE @@ -189,14 +112,13 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter { - Timber.d("Show time left lesson: $position") + timeLeft?.left != null -> { timetableItemTimeUntil.visibility = GONE with(timetableItemTimeLeft) { visibility = VISIBLE @@ -204,14 +126,13 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter { - Timber.d("Show just finished lesson: $position") + timeLeft?.isJustFinished == true -> { timetableItemTimeUntil.visibility = GONE timetableItemTimeLeft.visibility = VISIBLE timetableItemTimeLeft.text = root.context.getString(R.string.timetable_finished) @@ -225,9 +146,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter, - private val newList: List, - private val isFlagsDifferent: Boolean - ) : DiffUtil.Callback() { + companion object { + private val differ = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean = + when { + oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> { + oldItem.lesson.start == newItem.lesson.start + } + oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal -> { + oldItem.lesson.start == newItem.lesson.start + } + else -> oldItem == newItem + } - override fun getOldListSize() = oldList.size + override fun areContentsTheSame(oldItem: TimetableItem, newItem: TimetableItem) = + oldItem == newItem - override fun getNewListSize() = newList.size - - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition].id == newList[newItemPosition].id - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition] == newList[newItemPosition] && !isFlagsDifferent + override fun getChangePayload(oldItem: TimetableItem, newItem: TimetableItem): Any? { + return if (oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal) { + if (oldItem.lesson == newItem.lesson && oldItem.timeLeft != newItem.timeLeft) { + "time_left" + } else super.getChangePayload(oldItem, newItem) + } else super.getChangePayload(oldItem, newItem) + } + } } } 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 f59c6432c..fdd4aface 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 @@ -12,7 +12,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.enums.TimetableMode import io.github.wulkanowy.databinding.FragmentTimetableBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity @@ -20,11 +19,7 @@ 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.dpToPx -import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear -import io.github.wulkanowy.utils.openMaterialDatePicker +import io.github.wulkanowy.utils.* import java.time.LocalDate import javax.inject.Inject @@ -73,8 +68,6 @@ class TimetableFragment : BaseFragment(R.layout.fragme } override fun initView() { - timetableAdapter.onClickListener = presenter::onTimetableItemSelected - with(binding.timetableRecycler) { layoutManager = LinearLayoutManager(context) adapter = timetableAdapter @@ -110,18 +103,8 @@ class TimetableFragment : BaseFragment(R.layout.fragme } } - override fun updateData( - data: List, - showWholeClassPlanType: TimetableMode, - showGroupsInPlanType: Boolean, - showTimetableTimers: Boolean - ) { - timetableAdapter.submitList( - newTimetable = data.toMutableList(), - showGroupsInPlan = showGroupsInPlanType, - showTimers = showTimetableTimers, - showWholeClassPlan = showWholeClassPlanType - ) + override fun updateData(data: List) { + timetableAdapter.submitList(data) } override fun clearData() { @@ -214,7 +197,6 @@ class TimetableFragment : BaseFragment(R.layout.fragme } override fun onDestroyView() { - timetableAdapter.clearTimers() presenter.onDetachView() super.onDestroyView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt new file mode 100644 index 000000000..92716ace8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt @@ -0,0 +1,30 @@ +package io.github.wulkanowy.ui.modules.timetable + +import io.github.wulkanowy.data.db.entities.Timetable +import java.time.Duration + +sealed class TimetableItem(val type: TimetableItemType) { + + data class Small( + val lesson: Timetable, + val onClick: (Timetable) -> Unit, + ) : TimetableItem(TimetableItemType.SMALL) + + data class Normal( + val lesson: Timetable, + val showGroupsInPlan: Boolean, + val timeLeft: TimeLeft?, + val onClick: (Timetable) -> Unit, + ) : TimetableItem(TimetableItemType.NORMAL) +} + +data class TimeLeft( + val until: Duration?, + val left: Duration?, + val isJustFinished: Boolean, +) + +enum class TimetableItemType { + SMALL, + NORMAL, +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 87f5cac3d..ec3ef7b02 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.timetable -import android.annotation.SuppressLint import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.enums.TimetableMode @@ -10,25 +9,17 @@ 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.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach import timber.log.Timber +import java.time.Instant import java.time.LocalDate -import java.time.LocalDate.now -import java.time.LocalDate.of -import java.time.LocalDate.ofEpochDay +import java.time.LocalDate.* +import java.util.* import javax.inject.Inject +import kotlin.concurrent.timer class TimetablePresenter @Inject constructor( errorHandler: ErrorHandler, @@ -46,6 +37,8 @@ class TimetablePresenter @Inject constructor( private lateinit var lastError: Throwable + private var tickTimer: Timer? = null + fun onAttachView(view: TimetableView, date: Long?) { super.onAttachView(view) view.initView() @@ -106,11 +99,6 @@ class TimetablePresenter @Inject constructor( } } - fun onTimetableItemSelected(lesson: Timetable) { - Timber.i("Select timetable item ${lesson.id}") - view?.showTimetableDialog(lesson) - } - fun onAdditionalLessonsSwitchSelected(): Boolean { view?.openAdditionalLessonsView() return true @@ -148,12 +136,12 @@ class TimetablePresenter @Inject constructor( Status.LOADING -> { if (!it.data?.lessons.isNullOrEmpty()) { view?.run { + updateData(it.data!!.lessons) enableSwipe(true) showRefresh(true) showErrorView(false) showProgress(false) showContent(true) - updateData(it.data!!.lessons) } } } @@ -189,17 +177,62 @@ class TimetablePresenter @Inject constructor( } private fun updateData(lessons: List) { - view?.updateData( - showWholeClassPlanType = prefRepository.showWholeClassPlan, - showGroupsInPlanType = prefRepository.showGroupsInPlan, - showTimetableTimers = prefRepository.showTimetableTimers, - data = createItems(lessons) + tickTimer?.cancel() + + if (!prefRepository.showTimetableTimers) { + view?.updateData(createItems(lessons)) + } else { + tickTimer = timer(period = 2_000) { + view?.updateData(createItems(lessons)) + } + } + } + + private fun createItems(items: List): List { + val filteredItems = items + .filter { + if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) { + it.isStudentPlan + } else true + }.sortedWith( + compareBy({ item -> item.number }, { item -> !item.isStudentPlan }) + ) + + return filteredItems.mapIndexed { i, it -> + if (it.isStudentPlan) TimetableItem.Normal( + lesson = it, + showGroupsInPlan = prefRepository.showGroupsInPlan, + timeLeft = filteredItems.getTimeLeftForLesson(it, i), + onClick = ::onTimetableItemSelected + ) else TimetableItem.Small( + lesson = it, + onClick = ::onTimetableItemSelected + ) + } + } + + private fun List.getTimeLeftForLesson(lesson: Timetable, index: Int): TimeLeft { + val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(index)) + return TimeLeft( + until = lesson.until.plusMinutes(1).takeIf { isShowTimeUntil }, + left = lesson.left?.plusMinutes(1), + isJustFinished = lesson.isJustFinished, ) } - private fun createItems(items: List) = items.filter { item -> - if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) item.isStudentPlan else true - }.sortedWith(compareBy({ item -> item.number }, { item -> !item.isStudentPlan })) + private fun List.getPreviousLesson(position: Int): Instant? { + return filter { it.isStudentPlan } + .getOrNull(position - 1 - filterIndexed { i, item -> i < position && !item.isStudentPlan }.size) + ?.let { + if (!it.canceled && it.isStudentPlan) it.end + else null + } + } + + private fun onTimetableItemSelected(lesson: Timetable) { + Timber.i("Select timetable item ${lesson.id}") + view?.showTimetableDialog(lesson) + } private fun showErrorViewOnError(message: String, error: Throwable) { view?.run { @@ -227,7 +260,6 @@ class TimetablePresenter @Inject constructor( } } - @SuppressLint("DefaultLocale") private fun reloadNavigation() { view?.apply { showPreButton(!currentDate.minusDays(1).isHolidays) @@ -235,4 +267,10 @@ class TimetablePresenter @Inject constructor( updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalise()) } } + + override fun onDetachView() { + tickTimer?.cancel() + tickTimer = null + super.onDetachView() + } } 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 4f6af4b9e..8cfb26204 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 @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.timetable import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.enums.TimetableMode import io.github.wulkanowy.ui.base.BaseView import java.time.LocalDate @@ -13,12 +12,7 @@ interface TimetableView : BaseView { fun initView() - fun updateData( - data: List, - showWholeClassPlanType: TimetableMode, - showGroupsInPlanType: Boolean, - showTimetableTimers: Boolean - ) + fun updateData(data: List) fun updateNavigationDay(date: String) From a04ba4ae1022ca28a3e4eda438bb4d911f76db68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 13 Mar 2022 22:43:57 +0100 Subject: [PATCH 071/105] Login improvements (#1800) * Update sdk * Change default register variant name * Change symbol hint message and email template --- app/build.gradle | 2 +- app/src/main/res/values/api_hosts.xml | 2 +- app/src/main/res/values/strings.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 960096acc..8467dd018 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -178,7 +178,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.5.0" + implementation "io.github.wulkanowy:sdk:fba873461e" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index 158490471..b3b434e14 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -1,7 +1,7 @@ - Vulcan + Standardowa Opolska eSzkoła Gdańska Platforma Edukacyjna Lubelski Portal Oświatowy diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d565d65cc..5712a6cc0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,7 +59,7 @@ Invalid symbol Student not found. Validate the symbol and the chosen variation of the UONET+ register Selected student is already logged in - The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous screen. Wulkanowy does not detect pre-school students at the moment + The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous screen Select students to log in to the application Other options In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices @@ -71,7 +71,7 @@ Discord Send email Zgłoszenie: Problemy z logowaniem - Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nNazwa szkoły wraz z miejscowością i numer klasy: + Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nNazwa szkoły i miejscowość: Make sure you select the correct UONET+ register variation! I forgot my password Recover your account From 15537586c465ce1c0956735866e587e27208b293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 13 Mar 2022 22:47:54 +0100 Subject: [PATCH 072/105] Add exam date field to exam details dialog (#1801) --- .../wulkanowy/ui/modules/exam/ExamDialog.kt | 3 +- app/src/main/res/layout/dialog_exam.xml | 45 ++++++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt index 3de9874da..7b0ac90cf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt @@ -47,7 +47,8 @@ class ExamDialog : DialogFragment() { examDialogSubjectValue.text = exam.subject examDialogTypeValue.text = exam.type examDialogTeacherValue.text = exam.teacher - examDialogDateValue.text = exam.entryDate.toFormattedString() + examDialogEntryDateValue.text = exam.entryDate.toFormattedString() + examDialogDeadlineDateValue.text = exam.date.toFormattedString() examDialogDescriptionValue.text = exam.description.ifBlank { getString(R.string.all_no_data) } diff --git a/app/src/main/res/layout/dialog_exam.xml b/app/src/main/res/layout/dialog_exam.xml index 51153ac8e..f77ca553a 100644 --- a/app/src/main/res/layout/dialog_exam.xml +++ b/app/src/main/res/layout/dialog_exam.xml @@ -1,8 +1,10 @@ + android:layout_height="match_parent" + tools:context=".ui.modules.exam.ExamDialog"> + app:layout_constraintTop_toBottomOf="@id/examDialogDeadlineDateTitle" /> + + + + + app:layout_constraintTop_toBottomOf="@id/examDialogEntryDateValue" /> Date: Mon, 14 Mar 2022 00:38:40 +0100 Subject: [PATCH 073/105] Add the option to quickly add a calendar event from the exam details (#1802) * Extract intent utils to separate file * Add add to calendar button in exam details dialog * Set 8:00-8:45 start/end time --- .../wulkanowy/ui/modules/exam/ExamDialog.kt | 10 ++ .../wulkanowy/utils/ContextExtension.kt | 81 +------------- .../io/github/wulkanowy/utils/IntentUtils.kt | 100 ++++++++++++++++++ ...c_calendat_all.xml => ic_calendar_all.xml} | 0 .../main/res/layout/dialog_additional_add.xml | 2 +- app/src/main/res/layout/dialog_exam.xml | 40 ++++--- .../main/res/layout/dialog_homework_add.xml | 4 +- app/src/main/res/values/strings.xml | 1 + 8 files changed, 144 insertions(+), 94 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt rename app/src/main/res/drawable/{ic_calendat_all.xml => ic_calendar_all.xml} (100%) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt index 7b0ac90cf..41adc008a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt @@ -9,7 +9,9 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.databinding.DialogExamBinding import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.utils.openCalendarEventAdd import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalTime class ExamDialog : DialogFragment() { @@ -54,6 +56,14 @@ class ExamDialog : DialogFragment() { } examDialogClose.setOnClickListener { dismiss() } + examDialogAddToCalendar.setOnClickListener { + requireContext().openCalendarEventAdd( + title = "${exam.subject} - ${exam.type}", + description = exam.description, + start = exam.date.atTime(LocalTime.of(8, 0)), + end = exam.date.atTime(LocalTime.of(8, 45)), + ) + } } } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index ecd982a18..323e1e477 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -1,31 +1,17 @@ package io.github.wulkanowy.utils import android.annotation.SuppressLint -import android.content.ActivityNotFoundException import android.content.Context -import android.content.Intent -import android.graphics.Bitmap -import android.graphics.Color -import android.graphics.Paint -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import android.graphics.Rect -import android.graphics.Typeface -import android.net.Uri +import android.graphics.* import android.text.TextPaint import android.util.DisplayMetrics.DENSITY_DEFAULT -import androidx.annotation.AttrRes -import androidx.annotation.ColorInt -import androidx.annotation.ColorRes -import androidx.annotation.DrawableRes -import androidx.annotation.PluralsRes +import androidx.annotation.* import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.applyCanvas import androidx.core.graphics.drawable.RoundedBitmapDrawable import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.toBitmap -import io.github.wulkanowy.BuildConfig.APPLICATION_ID @ColorInt fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int { @@ -61,69 +47,6 @@ fun Context.getCompatBitmap(@DrawableRes drawableRes: Int, @ColorRes colorRes: I fun Context.getPlural(@PluralsRes pluralRes: Int, quantity: Int, vararg arguments: Any) = resources.getQuantityString(pluralRes, quantity, *arguments) -fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit = {}) { - Intent.parseUri(uri, 0).let { - try { - startActivity(it) - } catch (e: ActivityNotFoundException) { - onActivityNotFound(uri) - } - } -} - -fun Context.openAppInMarket(onActivityNotFound: (uri: String) -> Unit) { - openInternetBrowser("market://details?id=${APPLICATION_ID}") { - openInternetBrowser("https://github.com/wulkanowy/wulkanowy/releases", onActivityNotFound) - } -} - -fun Context.openEmailClient( - chooserTitle: String, - email: String, - subject: String, - body: String, - onActivityNotFound: () -> Unit = {} -) { - val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")).apply { - putExtra(Intent.EXTRA_EMAIL, arrayOf(email)) - putExtra(Intent.EXTRA_SUBJECT, subject) - putExtra(Intent.EXTRA_TEXT, body) - } - - if (intent.resolveActivity(packageManager) != null) { - startActivity(Intent.createChooser(intent, chooserTitle)) - } else onActivityNotFound() -} - -fun Context.openNavigation(location: String) { - val intentUri = Uri.parse("geo:0,0?q=${Uri.encode(location)}") - val intent = Intent(Intent.ACTION_VIEW, intentUri) - if (intent.resolveActivity(packageManager) != null) { - startActivity(intent) - } -} - -fun Context.openDialer(phone: String) { - val intentUri = Uri.parse("tel:$phone") - val intent = Intent(Intent.ACTION_DIAL, intentUri) - if (intent.resolveActivity(packageManager) != null) { - startActivity(intent) - } -} - -fun Context.shareText(text: String, subject: String?) { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, text) - if (subject != null) { - putExtra(Intent.EXTRA_SUBJECT, subject) - } - type = "text/plain" - } - val shareIntent = Intent.createChooser(sendIntent, null) - startActivity(shareIntent) -} - fun Context.dpToPx(dp: Float) = dp * resources.displayMetrics.densityDpi / DENSITY_DEFAULT @SuppressLint("DefaultLocale") diff --git a/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt new file mode 100644 index 000000000..1ef03f2e6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt @@ -0,0 +1,100 @@ +package io.github.wulkanowy.utils + +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.CalendarContract +import io.github.wulkanowy.BuildConfig +import java.time.LocalDateTime +import java.time.ZoneId + +fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit = {}) { + Intent.parseUri(uri, 0).let { + try { + startActivity(it) + } catch (e: ActivityNotFoundException) { + onActivityNotFound(uri) + } + } +} + +fun Context.openAppInMarket(onActivityNotFound: (uri: String) -> Unit) { + openInternetBrowser("market://details?id=${BuildConfig.APPLICATION_ID}") { + openInternetBrowser("https://github.com/wulkanowy/wulkanowy/releases", onActivityNotFound) + } +} + +fun Context.openEmailClient( + chooserTitle: String, + email: String, + subject: String, + body: String, + onActivityNotFound: () -> Unit = {} +) { + val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")).apply { + putExtra(Intent.EXTRA_EMAIL, arrayOf(email)) + putExtra(Intent.EXTRA_SUBJECT, subject) + putExtra(Intent.EXTRA_TEXT, body) + } + + if (intent.resolveActivity(packageManager) != null) { + startActivity(Intent.createChooser(intent, chooserTitle)) + } else onActivityNotFound() +} + +fun Context.openCalendarEventAdd( + title: String, + description: String, + start: LocalDateTime, + end: LocalDateTime? = null, + isAllDay: Boolean = false, + onActivityNotFound: (uri: String?) -> Unit = {}, +) { + val beginTime = start.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() + val endTime = end?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli() + + val intent = Intent(Intent.ACTION_INSERT) + .setData(CalendarContract.Events.CONTENT_URI) + .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime) + .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime) + .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay) + .putExtra(CalendarContract.Events.TITLE, title) + .putExtra(CalendarContract.Events.DESCRIPTION, description) + .putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY) + + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + onActivityNotFound(intent.dataString) + } +} + +fun Context.openNavigation(location: String) { + val intentUri = Uri.parse("geo:0,0?q=${Uri.encode(location)}") + val intent = Intent(Intent.ACTION_VIEW, intentUri) + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } +} + +fun Context.openDialer(phone: String) { + val intentUri = Uri.parse("tel:$phone") + val intent = Intent(Intent.ACTION_DIAL, intentUri) + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } +} + +fun Context.shareText(text: String, subject: String?) { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, text) + if (subject != null) { + putExtra(Intent.EXTRA_SUBJECT, subject) + } + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) +} diff --git a/app/src/main/res/drawable/ic_calendat_all.xml b/app/src/main/res/drawable/ic_calendar_all.xml similarity index 100% rename from app/src/main/res/drawable/ic_calendat_all.xml rename to app/src/main/res/drawable/ic_calendar_all.xml diff --git a/app/src/main/res/layout/dialog_additional_add.xml b/app/src/main/res/layout/dialog_additional_add.xml index 884018e55..54f031be9 100644 --- a/app/src/main/res/layout/dialog_additional_add.xml +++ b/app/src/main/res/layout/dialog_additional_add.xml @@ -33,7 +33,7 @@ android:layout_marginHorizontal="16dp" android:layout_marginTop="28dp" android:hint="@string/all_date" - app:startIconDrawable="@drawable/ic_calendat_all"> + app:startIconDrawable="@drawable/ic_calendar_all"> + + + app:startIconDrawable="@drawable/ic_calendar_all"> - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5712a6cc0..ee702251e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -637,6 +637,7 @@ Copied Undo Change + Add to calendar From 26e0f43fa08899ffdf57086bca722302b6abf992 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:28:26 +0000 Subject: [PATCH 074/105] Bump firebase-bom from 29.1.0 to 29.2.0 (#1803) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8467dd018..f2a578c8e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -233,7 +233,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.6.0' - playImplementation platform('com.google.firebase:firebase-bom:29.1.0') + playImplementation platform('com.google.firebase:firebase-bom:29.2.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 5ce30a300010b9d1445361a924dd89851cbd0d2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Mar 2022 00:25:11 +0000 Subject: [PATCH 075/105] Bump firebase-bom from 29.2.0 to 29.2.1 (#1804) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index f2a578c8e..86f7ee169 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -233,7 +233,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.6.0' - playImplementation platform('com.google.firebase:firebase-bom:29.2.0') + playImplementation platform('com.google.firebase:firebase-bom:29.2.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From c8d069c7875a64e4a00e98eb18e641dba45c6654 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Mar 2022 00:25:31 +0000 Subject: [PATCH 076/105] Bump hianalytics from 6.4.0.300 to 6.4.1.300 (#1805) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 86f7ee169..12698a085 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:20.6.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.4.0.300' + hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.4.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From f851a4d2c596095f80a6f34607e4be7af5214143 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Mar 2022 12:47:27 -0500 Subject: [PATCH 077/105] Bump huawei-publish-gradle-plugin from 1.3.1 to 1.3.3 (#1806) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1a97f58ce..6b8d14ed9 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { classpath 'com.huawei.agconnect:agcp:1.6.4.300' 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.1" + classpath "ru.cian:huawei-publish-gradle-plugin:1.3.3" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" From a07741b5c58767ed7c1e2c20043d45a654661c3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:49:32 +0000 Subject: [PATCH 078/105] Bump WhatTheStack from 1.0.0-alpha03 to 1.0.0-alpha04 (#1807) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 12698a085..d4e890843 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -248,7 +248,7 @@ dependencies { debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6' - debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha03' + debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha04' testImplementation "junit:junit:4.13.2" testImplementation "io.mockk:mockk:$mockk" From 8d8990761a0755ce4e8c53c7a3405cd2552a25fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Mar 2022 22:37:22 +0000 Subject: [PATCH 079/105] Bump firebase-bom from 29.2.1 to 29.3.0 (#1809) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d4e890843..73948bad5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -233,7 +233,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.6.0' - playImplementation platform('com.google.firebase:firebase-bom:29.2.1') + playImplementation platform('com.google.firebase:firebase-bom:29.3.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 042b66ca5cf45650fddc42523536317e8332b67d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Mar 2022 22:37:53 +0000 Subject: [PATCH 080/105] Bump core-splashscreen from 1.0.0-beta01 to 1.0.0-beta02 (#1810) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 73948bad5..7ae210675 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "androidx.core:core-ktx:1.7.0" - implementation 'androidx.core:core-splashscreen:1.0.0-beta01' + implementation 'androidx.core:core-splashscreen:1.0.0-beta02' implementation "androidx.activity:activity-ktx:1.4.0" implementation "androidx.appcompat:appcompat:1.4.1" implementation "androidx.fragment:fragment-ktx:1.4.1" From 20dde6e89605495f1481d0bf588cba1f3dd00878 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Sun, 27 Mar 2022 15:33:32 +0200 Subject: [PATCH 081/105] Resource refactor (#1589) --- .../java/io/github/wulkanowy/data/Resource.kt | 180 ++++++- .../wulkanowy/data/db/dao/StudentDao.kt | 4 + .../repositories/AdminMessageRepository.kt | 3 +- .../data/repositories/AttendanceRepository.kt | 2 + .../AttendanceSummaryRepository.kt | 7 +- .../CompletedLessonsRepository.kt | 2 + .../data/repositories/ConferenceRepository.kt | 7 +- .../data/repositories/ExamRepository.kt | 2 + .../data/repositories/GradeRepository.kt | 5 + .../repositories/GradeStatisticsRepository.kt | 9 +- .../data/repositories/HomeworkRepository.kt | 2 + .../repositories/LuckyNumberRepository.kt | 3 +- .../data/repositories/MessageRepository.kt | 14 +- .../repositories/MobileDeviceRepository.kt | 7 +- .../data/repositories/NoteRepository.kt | 2 + .../SchoolAnnouncementRepository.kt | 3 +- .../data/repositories/SchoolRepository.kt | 3 +- .../repositories/StudentInfoRepository.kt | 3 +- .../data/repositories/StudentRepository.kt | 9 + .../data/repositories/SubjectRepository.kt | 7 +- .../data/repositories/TeacherRepository.kt | 7 +- .../data/repositories/TimetableRepository.kt | 14 +- .../alarm/TimetableNotificationReceiver.kt | 13 +- .../sync/works/AttendanceSummaryWork.kt | 2 +- .../services/sync/works/AttendanceWork.kt | 2 +- .../sync/works/CompletedLessonWork.kt | 2 +- .../services/sync/works/ConferenceWork.kt | 2 +- .../wulkanowy/services/sync/works/ExamWork.kt | 2 +- .../sync/works/GradeStatisticsWork.kt | 3 +- .../services/sync/works/GradeWork.kt | 2 +- .../services/sync/works/HomeworkWork.kt | 2 +- .../services/sync/works/LuckyNumberWork.kt | 2 +- .../services/sync/works/MessageWork.kt | 2 +- .../wulkanowy/services/sync/works/NoteWork.kt | 2 +- .../sync/works/SchoolAnnouncementWork.kt | 2 +- .../services/sync/works/TeacherWork.kt | 3 +- .../services/sync/works/TimetableWork.kt | 2 +- .../github/wulkanowy/ui/base/BasePresenter.kt | 43 +- .../about/contributor/ContributorPresenter.kt | 20 +- .../modules/about/license/LicensePresenter.kt | 26 +- .../ui/modules/account/AccountPresenter.kt | 25 +- .../accountdetails/AccountDetailsPresenter.kt | 110 ++-- .../accountedit/AccountEditPresenter.kt | 52 +- .../accountquick/AccountQuickPresenter.kt | 25 +- .../modules/attendance/AttendancePresenter.kt | 135 ++--- .../summary/AttendanceSummaryFragment.kt | 2 +- .../summary/AttendanceSummaryPresenter.kt | 102 ++-- .../summary/AttendanceSummaryView.kt | 2 +- .../modules/conference/ConferencePresenter.kt | 72 +-- .../ui/modules/dashboard/DashboardFragment.kt | 2 +- .../modules/dashboard/DashboardPresenter.kt | 389 +++++++------- .../debug/logviewer/LogViewerPresenter.kt | 52 +- .../ui/modules/exam/ExamPresenter.kt | 108 ++-- .../ui/modules/grade/GradeAverageProvider.kt | 33 +- .../ui/modules/grade/GradePresenter.kt | 46 +- .../grade/details/GradeDetailsDialog.kt | 1 + .../grade/details/GradeDetailsPresenter.kt | 132 ++--- .../statistics/GradeStatisticsPresenter.kt | 134 ++--- .../grade/summary/GradeSummaryPresenter.kt | 85 ++- .../ui/modules/homework/HomeworkPresenter.kt | 113 ++-- .../homework/add/HomeworkAddPresenter.kt | 31 +- .../details/HomeworkDetailsPresenter.kt | 49 +- .../login/advanced/LoginAdvancedPresenter.kt | 62 +-- .../modules/login/form/LoginFormPresenter.kt | 75 ++- .../login/recover/LoginRecoverPresenter.kt | 61 ++- .../LoginStudentSelectPresenter.kt | 35 +- .../login/symbol/LoginSymbolPresenter.kt | 24 +- .../luckynumber/LuckyNumberPresenter.kt | 71 ++- .../history/LuckyNumberHistoryPresenter.kt | 102 ++-- .../LuckyNumberWidgetConfigurePresenter.kt | 17 +- .../LuckyNumberWidgetProvider.kt | 20 +- .../ui/modules/main/MainPresenter.kt | 29 +- .../preview/MessagePreviewPresenter.kt | 104 ++-- .../message/send/SendMessagePresenter.kt | 117 +++-- .../message/tab/MessageTabPresenter.kt | 116 ++-- .../mobiledevice/MobileDevicePresenter.kt | 94 ++-- .../token/MobileDeviceTokenPresenter.kt | 43 +- .../ui/modules/note/NotePresenter.kt | 84 ++- .../school/SchoolPresenter.kt | 53 +- .../teacher/TeacherPresenter.kt | 65 ++- .../SchoolAnnouncementPresenter.kt | 70 +-- .../studentinfo/StudentInfoPresenter.kt | 72 ++- .../modules/timetable/TimetablePresenter.kt | 80 ++- .../additional/AdditionalLessonsPresenter.kt | 74 ++- .../completed/CompletedLessonsPresenter.kt | 88 ++-- .../TimetableWidgetConfigurePresenter.kt | 17 +- .../timetablewidget/TimetableWidgetFactory.kt | 5 +- .../io/github/wulkanowy/utils/FlowUtils.kt | 96 ---- .../ResourceTest.kt} | 8 +- .../repositories/AttendanceRepositoryTest.kt | 23 +- .../CompletedLessonsRepositoryTest.kt | 23 +- .../data/repositories/ExamRemoteTest.kt | 23 +- .../data/repositories/GradeRepositoryTest.kt | 28 +- .../GradeStatisticsRepositoryTest.kt | 16 +- .../repositories/LuckyNumberRemoteTest.kt | 22 +- .../repositories/MessageRepositoryTest.kt | 19 +- .../MobileDeviceRepositoryTest.kt | 23 +- .../repositories/TimetableRepositoryTest.kt | 15 +- .../modules/grade/GradeAverageProviderTest.kt | 494 +++++++++++++----- .../github/wulkanowy/utils/ResourceUtils.kt | 14 + gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 269 ++++++---- 102 files changed, 2419 insertions(+), 2361 deletions(-) delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt rename app/src/test/java/io/github/wulkanowy/{utils/FlowUtilsKtTest.kt => data/ResourceTest.kt} (96%) create mode 100644 app/src/test/java/io/github/wulkanowy/utils/ResourceUtils.kt diff --git a/app/src/main/java/io/github/wulkanowy/data/Resource.kt b/app/src/main/java/io/github/wulkanowy/data/Resource.kt index 406440c83..44f8a1b48 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -1,23 +1,173 @@ package io.github.wulkanowy.data -data class Resource(val status: Status, val data: T?, val error: Throwable?) { - companion object { - fun success(data: T?): Resource { - return Resource(Status.SUCCESS, data, null) - } +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber - fun error(error: Throwable?, data: T? = null): Resource { - return Resource(Status.ERROR, data, error) - } +sealed class Resource { - fun loading(data: T? = null): Resource { - return Resource(Status.LOADING, data, null) - } + open class Loading : Resource() + + data class Intermediate(val data: T) : Loading() + + data class Success(val data: T) : Resource() + + data class Error(val error: Throwable) : Resource() +} + +val Resource.dataOrNull: T? + get() = when (this) { + is Resource.Success -> this.data + is Resource.Intermediate -> this.data + is Resource.Loading -> null + is Resource.Error -> null + } + +val Resource.errorOrNull: Throwable? + get() = when (this) { + is Resource.Error -> this.error + else -> null + } + +fun resourceFlow(block: suspend () -> T) = flow { + emit(Resource.Loading()) + emit(Resource.Success(block())) +}.catch { emit(Resource.Error(it)) } + +fun flatResourceFlow(block: suspend () -> Flow>) = flow { + emit(Resource.Loading()) + emitAll(block().filter { it is Resource.Intermediate || it !is Resource.Loading }) +}.catch { emit(Resource.Error(it)) } + +fun Resource.mapData(block: (T) -> U) = when (this) { + is Resource.Success -> Resource.Success(block(this.data)) + is Resource.Intermediate -> Resource.Intermediate(block(this.data)) + is Resource.Loading -> Resource.Loading() + is Resource.Error -> Resource.Error(this.error) +} + +fun Flow>.logResourceStatus(name: String, showData: Boolean = false) = onEach { + val description = when (it) { + is Resource.Loading -> "started" + is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else "" + is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else "" + is Resource.Error -> "exception occurred: ${it.error}" + } + Timber.i("$name: $description") +} + +fun Flow>.mapResourceData(block: (T) -> U) = map { + it.mapData(block) +} + +fun Flow>.onResourceData(block: suspend (T) -> Unit) = onEach { + when (it) { + is Resource.Success -> block(it.data) + is Resource.Intermediate -> block(it.data) + is Resource.Error, + is Resource.Loading -> Unit } } -enum class Status { - LOADING, - SUCCESS, - ERROR +fun Flow>.onResourceLoading(block: suspend () -> Unit) = onEach { + if (it is Resource.Loading) { + block() + } +} + +fun Flow>.onResourceIntermediate(block: suspend (T) -> Unit) = onEach { + if (it is Resource.Intermediate) { + block(it.data) + } +} + +fun Flow>.onResourceSuccess(block: suspend (T) -> Unit) = onEach { + if (it is Resource.Success) { + block(it.data) + } +} + +fun Flow>.onResourceError(block: (Throwable) -> Unit) = onEach { + if (it is Resource.Error) { + block(it.error) + } +} + +fun Flow>.onResourceNotLoading(block: () -> Unit) = onEach { + if (it !is Resource.Loading) { + block() + } +} + +suspend fun Flow>.toFirstResult() = filter { it !is Resource.Loading }.first() + +suspend fun Flow>.waitForResult() = takeWhile { it is Resource.Loading }.collect() + +inline fun networkBoundResource( + mutex: Mutex = Mutex(), + showSavedOnLoading: Boolean = true, + crossinline isResultEmpty: (ResultType) -> Boolean, + crossinline query: () -> Flow, + crossinline fetch: suspend (ResultType) -> RequestType, + crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, + crossinline onFetchFailed: (Throwable) -> Unit = { }, + crossinline shouldFetch: (ResultType) -> Boolean = { true }, + crossinline filterResult: (ResultType) -> ResultType = { it } +) = flow { + emit(Resource.Loading()) + + val data = query().first() + emitAll(if (shouldFetch(data)) { + val filteredResult = filterResult(data) + + if (showSavedOnLoading && !isResultEmpty(filteredResult)) { + emit(Resource.Intermediate(filteredResult)) + } + + try { + val newData = fetch(data) + mutex.withLock { saveFetchResult(query().first(), newData) } + query().map { Resource.Success(filterResult(it)) } + } catch (throwable: Throwable) { + onFetchFailed(throwable) + query().map { Resource.Error(throwable) } + } + } else { + query().map { Resource.Success(filterResult(it)) } + }) +} + +@JvmName("networkBoundResourceWithMap") +inline fun networkBoundResource( + mutex: Mutex = Mutex(), + showSavedOnLoading: Boolean = true, + crossinline isResultEmpty: (T) -> Boolean, + crossinline query: () -> Flow, + crossinline fetch: suspend (ResultType) -> RequestType, + crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, + crossinline onFetchFailed: (Throwable) -> Unit = { }, + crossinline shouldFetch: (ResultType) -> Boolean = { true }, + crossinline mapResult: (ResultType) -> T +) = flow { + emit(Resource.Loading()) + + val data = query().first() + emitAll(if (shouldFetch(data)) { + val mappedResult = mapResult(data) + + if (showSavedOnLoading && !isResultEmpty(mappedResult)) { + emit(Resource.Intermediate(mappedResult)) + } + try { + val newData = fetch(data) + mutex.withLock { saveFetchResult(query().first(), newData) } + query().map { Resource.Success(mapResult(it)) } + } catch (throwable: Throwable) { + onFetchFailed(throwable) + query().map { Resource.Error(throwable) } + } + } else { + query().map { Resource.Success(mapResult(it)) } + }) } 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 853a7cb74..87b3e0b32 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 @@ -33,6 +33,10 @@ abstract class StudentDao { @Query("SELECT * FROM Students") abstract suspend fun loadStudentsWithSemesters(): List + @Transaction + @Query("SELECT * FROM Students WHERE id = :id") + abstract suspend fun loadStudentWithSemestersById(id: Long): StudentWithSemesters? + @Query("UPDATE Students SET is_current = 1 WHERE id = :id") abstract suspend fun updateCurrent(id: Long) 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 e455411ea..c9655b722 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 @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.repositories 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.data.networkBoundResource import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.networkBoundResource import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -19,6 +19,7 @@ class AdminMessageRepository @Inject constructor( suspend fun getAdminMessages(student: Student) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, query = { adminMessageDao.loadAll() }, fetch = { adminMessageService.getAdminMessages() }, shouldFetch = { true }, 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 7184f5576..9aa6562a6 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 @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.entities.Attendance 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.utils.* @@ -36,6 +37,7 @@ class AttendanceRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, semester, start, end) 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 0857475f8..8e0709135 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 @@ -4,8 +4,12 @@ import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -28,6 +32,7 @@ class AttendanceSummaryRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) it.isEmpty() || forceRefresh || isExpired 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 2055f3f47..8f393cadb 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 @@ -4,6 +4,7 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex @@ -30,6 +31,7 @@ class CompletedLessonsRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, semester, start, end) 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 6af24d73f..83204cab0 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 @@ -5,8 +5,12 @@ import io.github.wulkanowy.data.db.entities.Conference 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import java.time.Instant @@ -32,6 +36,7 @@ class ConferenceRepository @Inject constructor( startDate: Instant = Instant.EPOCH, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) it.isEmpty() || forceRefresh || isExpired 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 c655c8001..faa80b93e 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 @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.entities.Exam 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow @@ -33,6 +34,7 @@ class ExamRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, semester, start, end) 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 f4087a887..f5f895d82 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 @@ -7,6 +7,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow @@ -36,6 +37,10 @@ class GradeRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { + //When details is empty and summary is not, app will not use summary cache - edge case + it.first.isEmpty() + }, shouldFetch = { (details, summaries) -> val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired 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 4d26c3126..9fa06c497 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 @@ -11,8 +11,12 @@ import io.github.wulkanowy.data.mappers.mapPartialToStatisticItems 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.util.* import javax.inject.Inject @@ -42,6 +46,7 @@ class GradeStatisticsRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = partialMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(partialCacheKey, semester) @@ -86,6 +91,7 @@ class GradeStatisticsRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = semesterMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(semesterCacheKey, semester) @@ -143,6 +149,7 @@ class GradeStatisticsRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = pointsMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) it.isEmpty() || forceRefresh || isExpired 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 900d9a68e..f564824de 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 @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.entities.Homework 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex @@ -32,6 +33,7 @@ class HomeworkRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, semester, start, end) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt index 41e824e57..87e8410f1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex @@ -29,6 +29,7 @@ class LuckyNumberRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, shouldFetch = { it == null || forceRefresh }, query = { luckyNumberDb.load(student.studentId, now()) }, fetch = { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt index 224c69bd7..8d6fd772a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt @@ -7,15 +7,12 @@ import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessagesDao -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.MessageWithAttachment -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Folder @@ -23,7 +20,6 @@ import io.github.wulkanowy.sdk.pojo.SentMessage 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 kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -59,6 +55,7 @@ class MessageRepository @Inject constructor( notify: Boolean = false, ): Flow>> = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, student, folder) @@ -106,8 +103,9 @@ class MessageRepository @Inject constructor( message: Message, markAsRead: Boolean = false, ): Flow> = networkBoundResource( + isResultEmpty = { it == null }, shouldFetch = { - checkNotNull(it, { "This message no longer exist!" }) + checkNotNull(it) { "This message no longer exist!" } Timber.d("Message content in db empty: ${it.message.content.isEmpty()}") it.message.unread || it.message.content.isEmpty() }, @@ -123,7 +121,7 @@ class MessageRepository @Inject constructor( } }, saveFetchResult = { old, (downloadedMessage, attachments) -> - checkNotNull(old, { "Fetched message no longer exist!" }) + checkNotNull(old) { "Fetched message no longer exist!" } messagesDb.updateAll(listOf(old.message.apply { id = old.message.id unread = !markAsRead 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 f825c36df..eda40cac4 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 @@ -6,9 +6,13 @@ 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.data.mappers.mapToMobileDeviceToken +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -30,6 +34,7 @@ class MobileDeviceRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) it.isEmpty() || forceRefresh || isExpired 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 19ad8f037..e5d7bc5cb 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 @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.entities.Note 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow @@ -30,6 +31,7 @@ class NoteRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( getRefreshKey(cacheKey, semester) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt index b6724ed34..cf7ac86cd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt @@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource 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 kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -31,6 +31,7 @@ class SchoolAnnouncementRepository @Inject constructor( forceRefresh: Boolean, notify: Boolean = false ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) it.isEmpty() || forceRefresh || isExpired 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 880a6a74c..7972ed084 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 @@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.networkBoundResource 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 kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -30,6 +30,7 @@ class SchoolRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, student) 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 1fa91dd46..efc82a772 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 @@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.StudentInfoDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -25,6 +25,7 @@ class StudentInfoRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, shouldFetch = { it == null || forceRefresh }, query = { studentInfoDao.loadStudentInfo(student.studentId) }, fetch = { 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 570f8bdb9..f006b7d28 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 @@ -73,6 +73,15 @@ class StudentRepository @Inject constructor( } } + suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) = + studentDb.loadStudentWithSemestersById(id)?.apply { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { + student.password = withContext(dispatchers.io) { + decrypt(student.password) + } + } + } + suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student { val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() 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 b9bca028f..3926122b3 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 @@ -4,8 +4,12 @@ import io.github.wulkanowy.data.db.dao.SubjectDao 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -27,6 +31,7 @@ class SubjectRepository @Inject constructor( forceRefresh: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) it.isEmpty() || forceRefresh || isExpired 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 6b615c7a7..acd71e1f4 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 @@ -4,8 +4,12 @@ import io.github.wulkanowy.data.db.dao.TeacherDao 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.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -27,6 +31,7 @@ class TeacherRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) it.isEmpty() || forceRefresh || isExpired 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 7534640c3..3145c2a23 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 @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableHeaderDao import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper @@ -30,6 +31,10 @@ class TimetableRepository @Inject constructor( private val cacheKey = "timetable" + enum class TimetableType { + NORMAL, ADDITIONAL + } + fun getTimetable( student: Student, semester: Semester, @@ -37,9 +42,16 @@ class TimetableRepository @Inject constructor( end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false, - notify: Boolean = false + notify: Boolean = false, + timetableType: TimetableType = TimetableType.NORMAL ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { + when (timetableType) { + TimetableType.NORMAL -> it.lessons.isEmpty() + TimetableType.ADDITIONAL -> it.additional.isEmpty() + } + }, shouldFetch = { (timetable, additional, headers) -> val refreshKey = getRefreshKey(cacheKey, semester, start, end) val isExpired = refreshHelper.shouldBeRefreshed(refreshKey) 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 c3ff1838e..01a583e13 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 @@ -10,19 +10,18 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow 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 import io.github.wulkanowy.utils.PendingIntentCompat -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.getCompatColor import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -59,7 +58,7 @@ class TimetableNotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Timber.d("Receiving intent... ${intent.toUri(0)}") - flowWithResource { + resourceFlow { val showStudentName = !studentRepository.isOneUniqueStudent() val student = studentRepository.getCurrentStudent(false) val studentId = intent.getIntExtra(STUDENT_ID, 0) @@ -69,9 +68,9 @@ class TimetableNotificationReceiver : BroadcastReceiver() { } else { Timber.d("Notification studentId($studentId) differs from current(${student.studentId})") } - }.onEach { - if (it.status == Status.ERROR) Timber.e(it.error!!) - }.launchIn(GlobalScope) + } + .onResourceError { Timber.e(it) } + .launchIn(GlobalScope) } private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) { 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 84b7017b1..55ce7e908 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 @@ -3,7 +3,7 @@ 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.AttendanceSummaryRepository -import io.github.wulkanowy.utils.waitForResult +import io.github.wulkanowy.data.waitForResult import javax.inject.Inject class AttendanceSummaryWork @Inject constructor( 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 9abf43e08..657f69638 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,9 +3,9 @@ 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.waitForResult import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification import io.github.wulkanowy.utils.previousOrSameSchoolDay -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject 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 c6ada9446..f898aa04b 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 @@ -3,9 +3,9 @@ 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.CompletedLessonsRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.waitForResult import java.time.LocalDate.now import javax.inject.Inject 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 becd74668..c85c00433 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,8 +3,8 @@ 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.waitForResult import io.github.wulkanowy.services.sync.notifications.NewConferenceNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject 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 39579dc8c..7071bce20 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,8 +3,8 @@ 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.waitForResult import io.github.wulkanowy.services.sync.notifications.NewExamNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject 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 2e915199e..ac35bc9a8 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 @@ -3,7 +3,8 @@ 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.GradeStatisticsRepository -import io.github.wulkanowy.utils.waitForResult +import io.github.wulkanowy.data.waitForResult + import javax.inject.Inject class GradeStatisticsWork @Inject constructor( 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 dd49f143c..ba21b8600 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,8 +3,8 @@ 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.waitForResult import io.github.wulkanowy.services.sync.notifications.NewGradeNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject 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 1385191b7..4cfe27d0d 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,9 +3,9 @@ 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.waitForResult import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject 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 f223a8546..668b1b6b8 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,8 +3,8 @@ 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.waitForResult import io.github.wulkanowy.services.sync.notifications.NewLuckyNumberNotification -import io.github.wulkanowy.utils.waitForResult import javax.inject.Inject class LuckyNumberWork @Inject constructor( 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 5bf326c7b..26fac1a2f 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,8 +4,8 @@ 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.waitForResult import io.github.wulkanowy.services.sync.notifications.NewMessageNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject 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 d66c3d661..df6e2b06b 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,8 +3,8 @@ 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.waitForResult import io.github.wulkanowy.services.sync.notifications.NewNoteNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject 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 9cee59024..805ceb3e4 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 @@ -3,8 +3,8 @@ 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.SchoolAnnouncementRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject 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 751fb6cc7..e7c72bf00 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 @@ -3,7 +3,8 @@ 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.TeacherRepository -import io.github.wulkanowy.utils.waitForResult +import io.github.wulkanowy.data.waitForResult + import javax.inject.Inject class TeacherWork @Inject constructor(private val teacherRepository: TeacherRepository) : Work { 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 575f9b961..29b1f13c7 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 @@ -3,9 +3,9 @@ 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.TimetableRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index 5cd5d0109..15c069f54 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -1,17 +1,10 @@ package io.github.wulkanowy.ui.base -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.StudentRepository -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber open class BasePresenter( @@ -37,28 +30,28 @@ open class BasePresenter( } fun onExpiredLoginSelected() { - flowWithResource { - val student = studentRepository.getCurrentStudent(false) - studentRepository.logoutStudent(student) + Timber.i("Attempt to switch the student after the session expires") - val students = studentRepository.getSavedStudents(false) - if (students.isNotEmpty()) { - Timber.i("Switching current student") - studentRepository.switchStudent(students[0]) + presenterScope.launch { + runCatching { + val student = studentRepository.getCurrentStudent(false) + studentRepository.logoutStudent(student) + + val students = studentRepository.getSavedStudents(false) + if (students.isNotEmpty()) { + Timber.i("Switching current student") + studentRepository.switchStudent(students[0]) + } } - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to switch the student after the session expires") - Status.SUCCESS -> { + .onFailure { + Timber.i("Switch student result: An exception occurred") + errorHandler.dispatch(it) + } + .onSuccess { Timber.i("Switch student result: Open login view") view?.openClearLoginView() } - Status.ERROR -> { - Timber.i("Switch student result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - }.launch("expired") + } } fun Flow.launch(individualJobTag: String = "load"): Job { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt index ef4b540e6..126bb2b48 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt @@ -1,13 +1,11 @@ package io.github.wulkanowy.ui.modules.about.contributor -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.pojos.Contributor import io.github.wulkanowy.data.repositories.AppCreatorRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import javax.inject.Inject class ContributorPresenter @Inject constructor( @@ -31,15 +29,11 @@ class ContributorPresenter @Inject constructor( } private fun loadData() { - flowWithResource { appCreatorRepository.getAppCreators() }.onEach { - when (it.status) { - Status.LOADING -> view?.showProgress(true) - Status.SUCCESS -> view?.run { - showProgress(false) - updateData(it.data!!) - } - Status.ERROR -> errorHandler.dispatch(it.error!!) - } - }.launch() + resourceFlow { appCreatorRepository.getAppCreators() } + .onResourceLoading { view?.showProgress(true) } + .onResourceSuccess { view?.updateData(it) } + .onResourceNotLoading { view?.showProgress(false) } + .onResourceError { errorHandler.dispatch(it) } + .launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt index 5368cc19d..5aa12a575 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt @@ -1,16 +1,12 @@ package io.github.wulkanowy.ui.modules.about.license import com.mikepenz.aboutlibraries.entity.Library -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.base.ErrorHandler import io.github.wulkanowy.utils.DispatchersProvider -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import timber.log.Timber import javax.inject.Inject class LicensePresenter @Inject constructor( @@ -30,18 +26,16 @@ class LicensePresenter @Inject constructor( } private fun loadData() { - flowWithResource { - withContext(dispatchers.io) { - view?.appLibraries.orEmpty() + presenterScope.launch { + runCatching { + withContext(dispatchers.io) { + view?.appLibraries.orEmpty() + } } - }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("License data load started") - Status.SUCCESS -> view?.updateData(it.data!!) - Status.ERROR -> errorHandler.dispatch(it.error!!) - } - }.afterLoading { + .onFailure { errorHandler.dispatch(it) } + .onSuccess { view?.updateData(it) } + view?.showProgress(false) - }.launch() + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index 7fe77ca7a..77c1ffe64 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -1,12 +1,13 @@ package io.github.wulkanowy.ui.modules.account -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -32,20 +33,10 @@ class AccountPresenter @Inject constructor( } private fun loadData() { - flowWithResource { studentRepository.getSavedStudents(false) } - .onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading account data started") - Status.SUCCESS -> { - Timber.i("Loading account result: Success") - view?.updateData(createAccountItems(it.data!!)) - } - Status.ERROR -> { - Timber.i("Loading account result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - } + resourceFlow { studentRepository.getSavedStudents(false) } + .logResourceStatus("load account data") + .onResourceSuccess { view?.updateData(createAccountItems(it)) } + .onResourceError(errorHandler::dispatch) .launch("load") } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt index 1f44cbbc3..5d68ff2e1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.account.accountdetails -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentRepository @@ -9,10 +8,6 @@ import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -51,40 +46,25 @@ class AccountDetailsPresenter @Inject constructor( } private fun loadData() { - flowWithResource { studentRepository.getSavedStudents() } - .map { studentWithSemesters -> - Resource( - data = studentWithSemesters.data?.single { it.student.id == studentId }, - status = studentWithSemesters.status, - error = studentWithSemesters.error - ) - } - .onEach { - when (it.status) { - Status.LOADING -> { - view?.run { - showProgress(true) - showContent(false) - } - Timber.i("Loading account details view started") - } - Status.SUCCESS -> { - Timber.i("Loading account details view result: Success") - studentWithSemesters = it.data - view?.run { - showAccountData(studentWithSemesters!!.student) - enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent) - showContent(true) - showErrorView(false) - } - } - Status.ERROR -> { - Timber.i("Loading account details view result: An exception occurred") - errorHandler.dispatch(it.error!!) - } + resourceFlow { studentRepository.getSavedStudentById(studentId ?: -1) } + .logResourceStatus("loading account details view") + .onResourceLoading { + view?.run { + showProgress(true) + showContent(false) } } - .afterLoading { view?.showProgress(false) } + .onResourceSuccess { + studentWithSemesters = it + view?.run { + showAccountData(studentWithSemesters!!.student) + enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent) + showContent(true) + showErrorView(false) + } + } + .onResourceNotLoading { view?.showProgress(false) } + .onResourceError(errorHandler::dispatch) .launch() } @@ -105,22 +85,12 @@ class AccountDetailsPresenter @Inject constructor( Timber.i("Select student ${studentWithSemesters!!.student.id}") - flowWithResource { studentRepository.switchStudent(studentWithSemesters!!) } - .onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to change a student") - Status.SUCCESS -> { - Timber.i("Change a student result: Success") - view?.recreateMainView() - } - Status.ERROR -> { - Timber.i("Change a student result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - }.afterLoading { - view?.popViewToMain() - }.launch("switch") + resourceFlow { studentRepository.switchStudent(studentWithSemesters!!) } + .logResourceStatus("change student") + .onResourceSuccess { view?.recreateMainView() } + .onResourceNotLoading { view?.popViewToMain() } + .onResourceError(errorHandler::dispatch) + .launch("switch") } fun onRemoveSelected() { @@ -131,7 +101,7 @@ class AccountDetailsPresenter @Inject constructor( fun onLogoutConfirm() { if (studentWithSemesters == null) return - flowWithResource { + resourceFlow { val studentToLogout = studentWithSemesters!!.student studentRepository.logoutStudent(studentToLogout) @@ -141,13 +111,13 @@ class AccountDetailsPresenter @Inject constructor( studentRepository.switchStudent(students[0]) } - return@flowWithResource students - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to logout user") - Status.SUCCESS -> view?.run { + students + } + .logResourceStatus("logout user") + .onResourceSuccess { + view?.run { when { - it.data!!.isEmpty() -> { + it.isEmpty() -> { Timber.i("Logout result: Open login view") syncManager.stopSyncWorker() openClearLoginView() @@ -162,18 +132,16 @@ class AccountDetailsPresenter @Inject constructor( } } } - Status.ERROR -> { - Timber.i("Logout result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceNotLoading { + if (studentWithSemesters?.student?.isCurrent == true) { + view?.popViewToMain() + } else { + view?.popViewToAccounts() } } - }.afterLoading { - if (studentWithSemesters?.student?.isCurrent == true) { - view?.popViewToMain() - } else { - view?.popViewToAccounts() - } - }.launch("logout") + .onResourceError(errorHandler::dispatch) + .launch("logout") } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt index 67ecdb5fc..c401158ea 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt @@ -1,15 +1,12 @@ package io.github.wulkanowy.ui.modules.account.accountedit -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -38,43 +35,26 @@ class AccountEditPresenter @Inject constructor( } private fun loadData() { - flowWithResource { - studentRepository.getStudentById(student.id, false).avatarColor - }.onEach { resource -> - when (resource.status) { - Status.LOADING -> Timber.i("Attempt to load student") - Status.SUCCESS -> { - view?.updateSelectedColorData(resource.data?.toInt()!!) - Timber.i("Attempt to load student: Success") - } - Status.ERROR -> { - Timber.i("Attempt to load student: An exception occurred") - errorHandler.dispatch(resource.error!!) - } - } - }.launch("load_data") + resourceFlow { studentRepository.getStudentById(student.id, false).avatarColor } + .logResourceStatus("load student") + .onResourceSuccess { view?.updateSelectedColorData(it.toInt()) } + .onResourceError(errorHandler::dispatch) + .launch("load_data") } fun changeStudentNickAndAvatar(nick: String, avatarColor: Int) { - flowWithResource { - val studentNick = - StudentNickAndAvatar(nick = nick.trim(), avatarColor = avatarColor.toLong()) - .apply { id = student.id } + resourceFlow { + val studentNick = StudentNickAndAvatar( + nick = nick.trim(), + avatarColor = avatarColor.toLong() + ).apply { id = student.id } + studentRepository.updateStudentNickAndAvatar(studentNick) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to change a student nick and avatar") - Status.SUCCESS -> { - Timber.i("Change a student nick and avatar result: Success") - view?.recreateMainView() - } - Status.ERROR -> { - Timber.i("Change a student nick and avatar result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } } - .afterLoading { view?.popView() } + .logResourceStatus("change student nick and avatar") + .onResourceSuccess { view?.recreateMainView() } + .onResourceNotLoading { view?.popView() } + .onResourceError(errorHandler::dispatch) .launch("update_student") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt index 39d8fce24..32c07f80a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt @@ -1,14 +1,11 @@ package io.github.wulkanowy.ui.modules.account.accountquick -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* 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 import io.github.wulkanowy.ui.modules.account.AccountItem -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -43,21 +40,11 @@ class AccountQuickPresenter @Inject constructor( return } - flowWithResource { studentRepository.switchStudent(studentWithSemesters) } - .onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to change a student") - Status.SUCCESS -> { - Timber.i("Change a student result: Success") - view?.recreateMainView() - } - Status.ERROR -> { - Timber.i("Change a student result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - } - .afterLoading { view?.popView() } + resourceFlow { studentRepository.switchStudent(studentWithSemesters) } + .logResourceStatus("change student") + .onResourceSuccess { view?.recreateMainView() } + .onResourceNotLoading { view?.popView() } + .onResourceError(errorHandler::dispatch) .launch("switch") } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 54d29bcf8..7fcbd002e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.attendance import android.annotation.SuppressLint -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.repositories.AttendanceRepository import io.github.wulkanowy.data.repositories.PreferencesRepository @@ -9,18 +9,7 @@ import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isExcusableOrNotExcused -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.previousOrSameSchoolDay -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -213,93 +202,77 @@ class AttendancePresenter @Inject constructor( var isParent = false - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() isParent = student.isParent val semester = semesterRepository.getCurrentSemester(student) attendanceRepository.getAttendance( - student, - semester, - currentDate, - currentDate, - forceRefresh + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh ) - }.onEach { - when (it.status) { - Status.LOADING -> { - view?.showExcuseButton(false) - if (!it.data.isNullOrEmpty()) { - val filteredAttendance = if (prefRepository.isShowPresent) { - it.data - } else { - it.data.filter { item -> !item.presence } - } - - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showErrorView(false) - showEmpty(filteredAttendance.isEmpty()) - showContent(filteredAttendance.isNotEmpty()) - updateData(filteredAttendance.sortedBy { item -> item.number }) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading attendance result: Success") - val filteredAttendance = if (prefRepository.isShowPresent) { - it.data.orEmpty() - } else { - it.data?.filter { item -> !item.presence }.orEmpty() - } - - isVulcanExcusedFunctionEnabled = - filteredAttendance.any { item -> item.excusable } - - view?.apply { - updateData(filteredAttendance.sortedBy { item -> item.number }) - showEmpty(filteredAttendance.isEmpty()) - showErrorView(false) - showContent(filteredAttendance.isNotEmpty()) - val anyExcusables = filteredAttendance.any { it.isExcusableOrNotExcused } - showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled)) - } - analytics.logEvent( - "load_data", - "type" to "attendance", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading attendance result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load attendance") + .onResourceLoading { + view?.showExcuseButton(false) + } + .mapResourceData { + if (prefRepository.isShowPresent) { + it + } else { + it.filter { item -> !item.presence } + }.sortedBy { item -> item.number } + } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showEmpty(it.isEmpty()) + showContent(it.isNotEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable } + val anyExcusables = it.any { it.isExcusableOrNotExcused } + view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled)) + + analytics.logEvent( + "load_data", + "type" to "attendance", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun excuseAbsence(reason: String?, toExcuseList: List) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason) }.onEach { - when (it.status) { - Status.LOADING -> view?.run { + when (it) { + is Resource.Loading -> view?.run { Timber.i("Excusing absence started") showProgress(true) showContent(false) showExcuseButton(false) } - Status.SUCCESS -> { + is Resource.Success -> { Timber.i("Excusing for absence result: Success") analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size) attendanceToExcuseList.clear() @@ -311,9 +284,9 @@ class AttendancePresenter @Inject constructor( } loadData(forceRefresh = true) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Excusing for absence result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) loadData() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt index dd1644a98..e750b8d57 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt @@ -74,7 +74,7 @@ class AttendanceSummaryFragment : binding.attendanceSummarySubjectsContainer.elevation = requireContext().dpToPx(1f) } - override fun updateSubjects(data: ArrayList) { + override fun updateSubjects(data: Collection) { with(subjectsAdapter) { clear() addAll(data) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt index 8b603837b..281999176 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.attendance.summary -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository @@ -10,9 +10,6 @@ import io.github.wulkanowy.data.repositories.SubjectRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.time.Month import javax.inject.Inject @@ -75,11 +72,9 @@ class AttendanceSummaryPresenter @Inject constructor( } private fun loadData(subjectId: Int, forceRefresh: Boolean = false) { - Timber.i("Loading attendance summary data started") - currentSubjectId = subjectId - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) @@ -89,47 +84,37 @@ class AttendanceSummaryPresenter @Inject constructor( subjectId = subjectId, forceRefresh = forceRefresh ) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - showErrorView(false) - updateDataSet(sortItems(it.data)) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading attendance summary result: Success") - view?.apply { - showErrorView(false) - showEmpty(it.data!!.isEmpty()) - showContent(it.data.isNotEmpty()) - updateDataSet(sortItems(it.data)) - } - analytics.logEvent( - "load_data", - "type" to "attendance_summary", - "items" to it.data!!.size, - "item_id" to subjectId - ) - } - Status.ERROR -> { - Timber.i("Loading attendance summary result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load attendance summary") + .mapResourceData(this::sortItems) + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateDataSet(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "attendance_summary", + "items" to it.size, + "item_id" to subjectId + ) } - }.launch() + .onResourceNotLoading { + view?.run { + showProgress(false) + showRefresh(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun sortItems(items: List) = items.sortedByDescending { item -> @@ -148,27 +133,20 @@ class AttendanceSummaryPresenter @Inject constructor( } private fun loadSubjects() { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) subjectRepository.getSubjects(student, semester) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading attendance summary subjects started") - Status.SUCCESS -> { - subjects = it.data!! - - Timber.i("Loading attendance summary subjects result: Success") - view?.run { - view?.updateSubjects(ArrayList(it.data.map { subject -> subject.name })) - showSubjects(true) - } - } - Status.ERROR -> { - Timber.i("Loading attendance summary subjects result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load attendance summary subjects") + .onResourceData { + subjects = it + view?.run { + view?.updateSubjects(it.map { subject -> subject.name }.toList()) + showSubjects(true) } } - }.launch("subjects") + .onResourceError(errorHandler::dispatch) + .launch("subjects") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt index 66f370c5c..99192f185 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt @@ -25,7 +25,7 @@ interface AttendanceSummaryView : BaseView { fun updateDataSet(data: List) - fun updateSubjects(data: ArrayList) + fun updateSubjects(data: Collection) fun showSubjects(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt index dab170daa..f53648930 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.conference -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.repositories.ConferenceRepository import io.github.wulkanowy.data.repositories.SemesterRepository @@ -8,9 +8,6 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -64,50 +61,39 @@ class ConferencePresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading conference data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) conferenceRepository.getConferences(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(it.data.sortedByDescending { conference -> conference.date }) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading conference result: Success") - view?.run { - updateData(it.data!!.sortedByDescending { conference -> conference.date }) - showContent(it.data.isNotEmpty()) - showEmpty(it.data.isEmpty()) - showErrorView(false) - } - analytics.logEvent( - "load_data", - "type" to "conferences", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading conference result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load conference data") + .mapResourceData { it.sortedByDescending { conference -> conference.date } } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "conferences", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } } 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 88c281ecd..65832bdb1 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 @@ -204,4 +204,4 @@ class DashboardFragment : BaseFragment(R.layout.fragme presenter.onDetachView() super.onDestroyView() } -} \ No newline at end of file +} 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 cad8d112b..c33955bc7 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 @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.dashboard -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student @@ -10,7 +9,6 @@ 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.* import kotlinx.coroutines.launch @@ -104,7 +102,7 @@ class DashboardPresenter @Inject constructor( forceRefresh: Boolean ) = dashboardTilesToLoad.filter { newItemToLoad -> dashboardLoadedTiles.none { it == newItemToLoad } || forceRefresh - || newItemToLoad == DashboardItem.Tile.ADMIN_MESSAGE + || newItemToLoad == DashboardItem.Tile.ADMIN_MESSAGE } private fun removeUnselectedTiles(tilesToLoad: List) { @@ -225,27 +223,26 @@ class DashboardPresenter @Inject constructor( val semester = semesterRepository.getCurrentSemester(student) val selectedTiles = preferencesRepository.selectedDashboardTiles + val flowSuccess = flowOf(Resource.Success(null)) val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh) - .map { - if (it.data == null) { - it.copy(data = LuckyNumber(0, LocalDate.now(), 0)) - } else it + .mapResourceData { + it ?: LuckyNumber(0, LocalDate.now(), 0) } - .takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowOf(null) + .takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowSuccess val messageFLow = messageRepository.getMessages( student = student, semester = semester, folder = MessageFolder.RECEIVED, forceRefresh = forceRefresh - ).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowOf(null) + ).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary( student = student, semester = semester, subjectId = -1, forceRefresh = forceRefresh - ).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowOf(null) + ).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess emitAll( combine( @@ -253,17 +250,13 @@ class DashboardPresenter @Inject constructor( messageFLow, attendanceFlow ) { luckyNumberResource, messageResource, attendanceResource -> - val error = - luckyNumberResource?.error ?: messageResource?.error - ?: attendanceResource?.error - error?.let { throw it } + val resList = listOf(luckyNumberResource, messageResource, attendanceResource) + resList.firstNotNullOfOrNull { it.errorOrNull }?.let { throw it } + val isLoading = resList.any { it is Resource.Loading } - val luckyNumber = luckyNumberResource?.data?.luckyNumber - val messageCount = messageResource?.data?.count { it.unread } - val attendancePercentage = attendanceResource?.data?.calculatePercentage() - - val isLoading = - luckyNumberResource?.status == Status.LOADING || messageResource?.status == Status.LOADING || attendanceResource?.status == Status.LOADING + val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber + val messageCount = messageResource.dataOrNull?.count { it.unread } + val attendancePercentage = attendanceResource.dataOrNull?.calculatePercentage() DashboardItem.HorizontalGroup( isLoading = isLoading, @@ -300,68 +293,65 @@ class DashboardPresenter @Inject constructor( } private fun loadGrades(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) gradeRepository.getGrades(student, semester, forceRefresh) - }.map { originalResource -> - val filteredSubjectWithGrades = originalResource.data?.first - .orEmpty() - .filter { it.date >= LocalDate.now().minusDays(7) } - .groupBy { it.subject } - .mapValues { entry -> - entry.value - .take(5) - .sortedByDescending { it.date } - } - .toList() - .sortedByDescending { (_, grades) -> grades[0].date } - .toMap() + } + .mapResourceData { (details, _) -> + val filteredSubjectWithGrades = details + .filter { it.date >= LocalDate.now().minusDays(7) } + .groupBy { it.subject } + .mapValues { entry -> + entry.value + .take(5) + .sortedByDescending { it.date } + } + .toList() + .sortedByDescending { (_, grades) -> grades[0].date } + .toMap() - Resource( - status = originalResource.status, - data = filteredSubjectWithGrades.takeIf { originalResource.data != null }, - error = originalResource.error - ) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard grades data started") - if (forceRefresh) return@onEach + filteredSubjectWithGrades + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard grades data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Grades( + subjectWithGrades = it.dataOrNull, + gradeTheme = preferencesRepository.gradeColorTheme, + isLoading = true + ), forceRefresh + ) - updateData( - DashboardItem.Grades( - subjectWithGrades = it.data, - gradeTheme = preferencesRepository.gradeColorTheme, - isLoading = true - ), forceRefresh - ) - - if (!it.data.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.GRADES + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.GRADES + } + } + is Resource.Success -> { + Timber.i("Loading dashboard grades result: Success") + updateData( + DashboardItem.Grades( + subjectWithGrades = it.data, + gradeTheme = preferencesRepository.gradeColorTheme + ), + forceRefresh + ) + } + is Resource.Error -> { + Timber.i("Loading dashboard grades result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Grades(error = it.error), forceRefresh) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard grades result: Success") - updateData( - DashboardItem.Grades( - subjectWithGrades = it.data, - gradeTheme = preferencesRepository.gradeColorTheme - ), - forceRefresh - ) - } - Status.ERROR -> { - Timber.i("Loading dashboard grades result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData(DashboardItem.Grades(error = it.error), forceRefresh) - } } - }.launchWithUniqueRefreshJob("dashboard_grades", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_grades", forceRefresh) } private fun loadLessons(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) val date = LocalDate.now().nextOrSameSchoolDay @@ -372,40 +362,41 @@ class DashboardPresenter @Inject constructor( end = date.plusDays(1), forceRefresh = forceRefresh ) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard lessons data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Lessons(it.dataOrNull, isLoading = true), + forceRefresh + ) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard lessons data started") - if (forceRefresh) return@onEach - updateData( - DashboardItem.Lessons(it.data, isLoading = true), - forceRefresh - ) - - if (!it.data?.lessons.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.LESSONS + if (!it.dataOrNull?.lessons.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.LESSONS + } + } + is Resource.Success -> { + Timber.i("Loading dashboard lessons result: Success") + updateData( + DashboardItem.Lessons(it.data), forceRefresh + ) + } + is Resource.Error -> { + Timber.i("Loading dashboard lessons result: An exception occurred") + errorHandler.dispatch(it.error) + updateData( + DashboardItem.Lessons(error = it.error), forceRefresh + ) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard lessons result: Success") - updateData( - DashboardItem.Lessons(it.data), forceRefresh - ) - } - Status.ERROR -> { - Timber.i("Loading dashboard lessons result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData( - DashboardItem.Lessons(error = it.error), forceRefresh - ) - } } - }.launchWithUniqueRefreshJob("dashboard_lessons", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_lessons", forceRefresh) } private fun loadHomework(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) val date = LocalDate.now().nextOrSameSchoolDay @@ -416,73 +407,79 @@ class DashboardPresenter @Inject constructor( end = date, forceRefresh = forceRefresh ) - }.map { homeworkResource -> - val currentDate = LocalDate.now() + } + .mapResourceData { homework -> + val currentDate = LocalDate.now() - val filteredHomework = homeworkResource.data - ?.filter { (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone } - ?.sortedBy { it.date } + val filteredHomework = homework.filter { + (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone + }.sortedBy { it.date } - homeworkResource.copy(data = filteredHomework) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard homework data started") - if (forceRefresh) return@onEach - updateData( - DashboardItem.Homework(it.data ?: emptyList(), isLoading = true), - forceRefresh - ) + filteredHomework + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard homework data started") + if (forceRefresh) return@onEach + val data = it.dataOrNull.orEmpty() + updateData( + DashboardItem.Homework(data, isLoading = true), + forceRefresh + ) - if (!it.data.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.HOMEWORK + if (data.isNotEmpty()) { + firstLoadedItemList += DashboardItem.Type.HOMEWORK + } + } + is Resource.Success -> { + Timber.i("Loading dashboard homework result: Success") + updateData(DashboardItem.Homework(it.data), forceRefresh) + } + is Resource.Error -> { + Timber.i("Loading dashboard homework result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Homework(error = it.error), forceRefresh) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard homework result: Success") - updateData(DashboardItem.Homework(it.data ?: emptyList()), forceRefresh) - } - Status.ERROR -> { - Timber.i("Loading dashboard homework result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData(DashboardItem.Homework(error = it.error), forceRefresh) - } } - }.launchWithUniqueRefreshJob("dashboard_homework", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_homework", forceRefresh) } private fun loadSchoolAnnouncements(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard announcements data started") - if (forceRefresh) return@onEach - updateData( - DashboardItem.Announcements(it.data ?: emptyList(), isLoading = true), - forceRefresh - ) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard announcements data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true), + forceRefresh + ) - if (!it.data.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS + } + } + is Resource.Success -> { + Timber.i("Loading dashboard announcements result: Success") + updateData(DashboardItem.Announcements(it.data), forceRefresh) + } + is Resource.Error -> { + Timber.i("Loading dashboard announcements result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Announcements(error = it.error), forceRefresh) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard announcements result: Success") - updateData(DashboardItem.Announcements(it.data ?: emptyList()), forceRefresh) - } - Status.ERROR -> { - Timber.i("Loading dashboard announcements result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData(DashboardItem.Announcements(error = it.error), forceRefresh) - } } - }.launchWithUniqueRefreshJob("dashboard_announcements", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_announcements", forceRefresh) } private fun loadExams(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) examRepository.getExams( @@ -493,40 +490,37 @@ class DashboardPresenter @Inject constructor( forceRefresh = forceRefresh ) } - .map { examResource -> - val sortedExams = examResource.data?.sortedBy { it.date } - - examResource.copy(data = sortedExams) - } + .mapResourceData { exams -> exams.sortedBy { exam -> exam.date } } .onEach { - when (it.status) { - Status.LOADING -> { + when (it) { + is Resource.Loading -> { Timber.i("Loading dashboard exams data started") if (forceRefresh) return@onEach updateData( - DashboardItem.Exams(it.data.orEmpty(), isLoading = true), + DashboardItem.Exams(it.dataOrNull.orEmpty(), isLoading = true), forceRefresh ) - if (!it.data.isNullOrEmpty()) { + if (!it.dataOrNull.isNullOrEmpty()) { firstLoadedItemList += DashboardItem.Type.EXAMS } } - Status.SUCCESS -> { + is Resource.Success -> { Timber.i("Loading dashboard exams result: Success") - updateData(DashboardItem.Exams(it.data ?: emptyList()), forceRefresh) + updateData(DashboardItem.Exams(it.data), forceRefresh) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Loading dashboard exams result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) updateData(DashboardItem.Exams(error = it.error), forceRefresh) } } - }.launchWithUniqueRefreshJob("dashboard_exams", forceRefresh) + } + .launchWithUniqueRefreshJob("dashboard_exams", forceRefresh) } private fun loadConferences(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) conferenceRepository.getConferences( @@ -535,59 +529,62 @@ class DashboardPresenter @Inject constructor( forceRefresh = forceRefresh, startDate = Instant.now(), ) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard conferences data started") - if (forceRefresh) return@onEach - updateData( - DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true), - forceRefresh - ) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard conferences data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true), + forceRefresh + ) - if (!it.data.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.CONFERENCES + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.CONFERENCES + } + } + is Resource.Success -> { + Timber.i("Loading dashboard conferences result: Success") + updateData(DashboardItem.Conferences(it.data), forceRefresh) + } + is Resource.Error -> { + Timber.i("Loading dashboard conferences result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Conferences(error = it.error), forceRefresh) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard conferences result: Success") - updateData(DashboardItem.Conferences(it.data ?: emptyList()), forceRefresh) - } - Status.ERROR -> { - Timber.i("Loading dashboard conferences result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData(DashboardItem.Conferences(error = it.error), forceRefresh) - } } - }.launchWithUniqueRefreshJob("dashboard_conferences", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_conferences", forceRefresh) } private fun loadAdminMessage(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { adminMessageRepository.getAdminMessages(student) } - .map { - val isDismissed = it.data?.id in preferencesRepository.dismissedAdminMessageIds - it.copy(data = it.data.takeUnless { isDismissed }) + flatResourceFlow { adminMessageRepository.getAdminMessages(student) } + .filter { + val data = it.dataOrNull ?: return@filter true + val isDismissed = data.id in preferencesRepository.dismissedAdminMessageIds + !isDismissed } .onEach { - when (it.status) { - Status.LOADING -> { + when (it) { + is Resource.Loading -> { Timber.i("Loading dashboard admin message data started") if (forceRefresh) return@onEach updateData(DashboardItem.AdminMessages(), forceRefresh) } - Status.SUCCESS -> { + is Resource.Success -> { Timber.i("Loading dashboard admin message result: Success") updateData( dashboardItem = DashboardItem.AdminMessages(adminMessage = it.data), forceRefresh = forceRefresh ) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Loading dashboard admin message result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) updateData( dashboardItem = DashboardItem.AdminMessages( - adminMessage = it.data, + adminMessage = null, error = it.error ), forceRefresh = forceRefresh @@ -740,7 +737,7 @@ class DashboardPresenter @Inject constructor( if (forceRefresh) { onEach { - if (it.status == Status.SUCCESS) { + if (it is Resource.Success) { cancelJobs(jobName) } }.launch(jobName) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt index 4310ff87c..7adb56b8c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.debug.logviewer -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.repositories.LoggerRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -23,19 +23,21 @@ class LogViewerPresenter @Inject constructor( } fun onShareLogsSelected(): Boolean { - flowWithResource { loggerRepository.getLogFiles() }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Loading logs files started") - Status.SUCCESS -> { - Timber.i("Loading logs files result: ${it.data!!.joinToString { file -> file.name }}") - view?.shareLogs(it.data) - } - Status.ERROR -> { - Timber.i("Loading logs files result: An exception occurred") - errorHandler.dispatch(it.error!!) + resourceFlow { loggerRepository.getLogFiles() } + .onEach { + when (it) { + is Resource.Loading -> Timber.d("Loading logs files started") + is Resource.Success -> { + Timber.i("Loading logs files result: ${it.data.joinToString { file -> file.name }}") + view?.shareLogs(it.data) + } + is Resource.Error -> { + Timber.i("Loading logs files result: An exception occurred") + errorHandler.dispatch(it.error) + } } } - }.launch("share") + .launch("share") return true } @@ -44,18 +46,20 @@ class LogViewerPresenter @Inject constructor( } private fun loadLogFile() { - flowWithResource { loggerRepository.getLastLogLines() }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Loading last log file started") - Status.SUCCESS -> { - Timber.i("Loading last log file result: load ${it.data!!.size} lines") - view?.setLines(it.data) - } - Status.ERROR -> { - Timber.i("Loading last log file result: An exception occurred") - errorHandler.dispatch(it.error!!) + resourceFlow { loggerRepository.getLastLogLines() } + .onEach { + when (it) { + is Resource.Loading -> Timber.d("Loading last log file started") + is Resource.Success -> { + Timber.i("Loading last log file result: load ${it.data.size} lines") + view?.setLines(it.data) + } + is Resource.Error -> { + Timber.i("Loading last log file result: An exception occurred") + errorHandler.dispatch(it.error) + } } } - }.launch("file") + .launch("file") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index 582641fcf..99b0bcb87 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -1,21 +1,13 @@ package io.github.wulkanowy.ui.modules.exam -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -86,61 +78,57 @@ class ExamPresenter @Inject constructor( flow { val student = studentRepository.getCurrentStudent() emit(semesterRepository.getCurrentSemester(student)) - }.catch { - Timber.i("Loading semester result: An exception occurred") - }.onEach { - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }.launch("holidays") + } + .catch { Timber.i("Loading semester result: An exception occurred") } + .onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + } + .launch("holidays") } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading exam data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - examRepository.getExams(student, semester, currentDate.monday, currentDate.sunday, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(createExamItems(it.data)) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading exam result: Success") - view?.apply { - updateData(createExamItems(it.data!!)) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "exam", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading exam result: An exception occurred") - errorHandler.dispatch(it.error!!) + examRepository.getExams( + student = student, + semester = semester, + start = currentDate.monday, + end = currentDate.sunday, + forceRefresh = forceRefresh + ) + } + .logResourceStatus("load exam data") + .mapResourceData { createExamItems(it) } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "exam", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -181,8 +169,10 @@ class ExamPresenter @Inject constructor( view?.apply { showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) - updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.sunday.toFormattedString("dd.MM")) + updateNavigationWeek( + "${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM") + ) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 0c18ce95e..b6733d4f2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.grade -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester @@ -13,12 +12,10 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.* import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map import javax.inject.Inject @OptIn(FlowPreview::class) @@ -35,7 +32,7 @@ class GradeAverageProvider @Inject constructor( private val isOptionalArithmeticAverage get() = preferencesRepository.isOptionalArithmeticAverage fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) = - flowWithResourceIn { + flatResourceFlow { val semesters = semesterRepository.getSemesters(student) when (preferencesRepository.gradeAverageMode) { @@ -81,17 +78,17 @@ class GradeAverageProvider @Inject constructor( val firstSemesterGradeSubjects = getGradeSubjects(student, firstSemester, forceRefresh) return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject -> - if (firstSemesterGradeSubject.status == Status.ERROR) { + if (firstSemesterGradeSubject.errorOrNull != null) { return@combine firstSemesterGradeSubject } val isAnyVulcanAverageInFirstSemester = - firstSemesterGradeSubject.data.orEmpty().any { it.isVulcanAverage } + firstSemesterGradeSubject.dataOrNull.orEmpty().any { it.isVulcanAverage } val isAnyVulcanAverageInSecondSemester = - secondSemesterGradeSubject.data.orEmpty().any { it.isVulcanAverage } + secondSemesterGradeSubject.dataOrNull.orEmpty().any { it.isVulcanAverage } - val updatedData = secondSemesterGradeSubject.data?.map { secondSemesterSubject -> - val firstSemesterSubject = firstSemesterGradeSubject.data.orEmpty() + val updatedData = secondSemesterGradeSubject.dataOrNull?.map { secondSemesterSubject -> + val firstSemesterSubject = firstSemesterGradeSubject.dataOrNull.orEmpty() .singleOrNull { it.subject == secondSemesterSubject.subject } val updatedAverage = if (averageMode == ALL_YEAR) { @@ -113,7 +110,7 @@ class GradeAverageProvider @Inject constructor( } secondSemesterSubject.copy(average = updatedAverage) } - secondSemesterGradeSubject.copy(data = updatedData) + secondSemesterGradeSubject.mapData { updatedData!! } } } @@ -166,17 +163,17 @@ class GradeAverageProvider @Inject constructor( val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh) - .map { res -> - val (details, summaries) = res.data ?: null to null - val isAnyAverage = summaries.orEmpty().any { it.average != .0 } - val allGrades = details.orEmpty().groupBy { it.subject } + .mapResourceData { res -> + val (details, summaries) = res + val isAnyAverage = summaries.any { it.average != .0 } + val allGrades = details.groupBy { it.subject } - val items = summaries?.emulateEmptySummaries( + val items = summaries.emulateEmptySummaries( student = student, semester = semester, grades = allGrades.toList(), calcAverage = isAnyAverage - )?.map { summary -> + ).map { summary -> val grades = allGrades[summary.subject].orEmpty() GradeSubject( subject = summary.subject, @@ -190,7 +187,7 @@ class GradeAverageProvider @Inject constructor( ) } - Resource(res.status, items, res.error) + items } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index 76e88bcdb..0ae6521cf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -1,16 +1,16 @@ package io.github.wulkanowy.ui.modules.grade -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.getCurrentOrLast -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -99,32 +99,26 @@ class GradePresenter @Inject constructor( } private fun loadData() { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() semesterRepository.getSemesters(student, refreshOnNoCurrent = true) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading grade data started") - Status.SUCCESS -> { - val current = it.data!!.getCurrentOrLast() - selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex - schoolYear = current.schoolYear - semesters = it.data.filter { semester -> semester.diaryId == current.diaryId } - view?.setCurrentSemesterName(current.semesterName, schoolYear) - - view?.run { - Timber.i("Loading grade result: Attempt load index $currentPageIndex") - loadChild(currentPageIndex) - showErrorView(false) - showSemesterSwitch(true) - } - } - Status.ERROR -> { - Timber.i("Loading grade result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load grade data") + .onResourceData { + val current = it.getCurrentOrLast() + selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex + schoolYear = current.schoolYear + semesters = it.filter { semester -> semester.diaryId == current.diaryId } + view?.setCurrentSemesterName(current.semesterName, schoolYear) + view?.run { + Timber.i("Loading grade data: Attempt load index $currentPageIndex") + loadChild(currentPageIndex) + showErrorView(false) + showSemesterSwitch(true) } } - }.launch() + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt index 3c747b949..34594111f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt @@ -12,6 +12,7 @@ import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.databinding.DialogGradeBinding import io.github.wulkanowy.utils.* + class GradeDetailsDialog : DialogFragment() { private var binding: DialogGradeBinding by lifecycleAwareVariable() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index 9141e0438..746601a68 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.details -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.enums.GradeExpandMode import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC @@ -14,12 +14,8 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeSubject import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -71,7 +67,7 @@ class GradeDetailsPresenter @Inject constructor( } fun onMarkAsReadSelected(): Boolean { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semesters = semesterRepository.getSemesters(student) val semester = semesters.first { item -> item.semesterId == currentSemesterId } @@ -79,19 +75,11 @@ class GradeDetailsPresenter @Inject constructor( Timber.i("Mark as read ${unreadGrades.size} grades") gradeRepository.updateGrades(unreadGrades.map { it.apply { isRead = true } }) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Select mark grades as read") - Status.SUCCESS -> { - Timber.i("Mark as read result: Success") - loadData(currentSemesterId, false) - } - Status.ERROR -> { - Timber.i("Mark as read result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - }.launch("mark") + } + .logResourceStatus("mark grades as read") + .onResourceSuccess { loadData(currentSemesterId, false) } + .onResourceError(errorHandler::dispatch) + .launch("mark") return true } @@ -138,71 +126,49 @@ class GradeDetailsPresenter @Inject constructor( } private fun loadData(semesterId: Int, forceRefresh: Boolean) { - Timber.i("Loading grade details data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) - }.onEach { - Timber.d("Loading grade details status: ${it.status}, data: ${it.data != null}") - when (it.status) { - Status.LOADING -> { - val items = createGradeItems(it.data.orEmpty()) - if (items.isNotEmpty()) { - Timber.i("Loading grade details result: load cached data") - view?.run { - updateNewGradesAmount(it.data.orEmpty()) - enableSwipe(true) - showRefresh(true) - showProgress(false) - showEmpty(false) - showContent(true) - updateData( - data = items, - expandMode = preferencesRepository.gradeExpandMode, - gradeColorTheme = preferencesRepository.gradeColorTheme - ) - notifyParentDataLoaded(semesterId) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading grade details result: Success") - updateNewGradesAmount(it.data!!) + } + .logResourceStatus("load grade details") + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateNewGradesAmount(it) updateMarkAsDoneButton() - val items = createGradeItems(it.data) - view?.run { - showEmpty(items.isEmpty()) - showErrorView(false) - showContent(items.isNotEmpty()) - updateData( - data = items, - expandMode = preferencesRepository.gradeExpandMode, - gradeColorTheme = preferencesRepository.gradeColorTheme - ) - } - analytics.logEvent( - "load_data", - "type" to "grade_details", - "items" to it.data.size + updateData( + data = createGradeItems(it), + expandMode = preferencesRepository.gradeExpandMode, + preferencesRepository.gradeColorTheme ) } - Status.ERROR -> { - Timber.i("Loading grade details result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "grade_details", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showRefresh(false) + showProgress(false) + notifyParentDataLoaded(semesterId) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) + .catch { + errorHandler.dispatch(it) + view?.notifyParentDataLoaded(semesterId) } - }.catch { - errorHandler.dispatch(it) - view?.notifyParentDataLoaded(semesterId) - }.launch() + .onResourceError(errorHandler::dispatch) + .launch() } private fun updateNewGradesAmount(grades: List) { @@ -267,15 +233,9 @@ class GradeDetailsPresenter @Inject constructor( } private fun updateGrade(grade: Grade) { - flowWithResource { gradeRepository.updateGrade(grade) }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to update grade ${grade.id}") - Status.SUCCESS -> Timber.i("Update grade result: Success") - Status.ERROR -> { - Timber.i("Update grade result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - }.launch("update") + resourceFlow { gradeRepository.updateGrade(grade) } + .logResourceStatus("update grade result ${grade.id}") + .onResourceError(errorHandler::dispatch) + .launch("update") } } 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 e536f473a..aa0e5999e 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 @@ -1,19 +1,12 @@ package io.github.wulkanowy.ui.modules.grade.statistics -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.pojos.GradeStatisticsItem -import io.github.wulkanowy.data.repositories.GradeStatisticsRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository -import io.github.wulkanowy.data.repositories.SemesterRepository -import io.github.wulkanowy.data.repositories.StudentRepository -import io.github.wulkanowy.data.repositories.SubjectRepository +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.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -125,33 +118,26 @@ class GradeStatisticsPresenter @Inject constructor( } private fun loadSubjects() { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) subjectRepository.getSubjects(student, semester) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading grade stats subjects started") - Status.SUCCESS -> { - subjects = requireNotNull(it.data) - Timber.i("Loading grade stats subjects result: Success") - - view?.run { - showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) - updateSubjects( - data = it.data.map { subject -> subject.name }, - selectedIndex = it.data.indexOfFirst { subject -> - subject.name == currentSubjectName - }, - ) - } - } - Status.ERROR -> { - Timber.i("Loading grade stats subjects result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load grade stats subjects") + .onResourceData { + subjects = it + view?.run { + showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) + updateSubjects( + data = it.map { subject -> subject.name }, + selectedIndex = it.indexOfFirst { subject -> + subject.name == currentSubjectName + }, + ) } } - }.launch("subjects") + .onResourceError(errorHandler::dispatch) + .launch("subjects") } private fun loadDataByType( @@ -168,7 +154,7 @@ class GradeStatisticsPresenter @Inject constructor( else -> subjectName } - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semesters = semesterRepository.getSemesters(student) val semester = semesters.first { item -> item.semesterId == semesterId } @@ -201,58 +187,43 @@ class GradeStatisticsPresenter @Inject constructor( } } } - }.onEach { - when (it.status) { - Status.LOADING -> { - val isNoContent = it.data == null || checkIsNoContent(it.data, type) - if (!isNoContent) { - view?.run { - showEmpty(isNoContent) - showErrorView(false) - enableSwipe(true) - showRefresh(true) - showProgress(false) - updateData( - newItems = if (isNoContent) emptyList() else it.data!!, - newTheme = preferencesRepository.gradeColorTheme, - showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList, - ) - showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading grade stats result: Success") - view?.run { - val isNoContent = checkIsNoContent(it.data!!, type) - showEmpty(isNoContent) - showErrorView(false) - updateData( - newItems = if (isNoContent) emptyList() else it.data, - newTheme = preferencesRepository.gradeColorTheme, - showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList, - ) - showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) - } - analytics.logEvent( - "load_data", - "type" to "grade_statistics", - "items" to it.data!!.size + } + .logResourceStatus("load grade stats data") + .mapResourceData { + val isNoContent = checkIsNoContent(it, type) + if (isNoContent) emptyList() else it + } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showEmpty(it.isEmpty()) + updateData( + newItems = it, + newTheme = preferencesRepository.gradeColorTheme, + showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList ) } - Status.ERROR -> { - Timber.i("Loading grade stats result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "grade_statistics", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showRefresh(false) + showProgress(false) + notifyParentDataLoaded(semesterId) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) - } - }.launch("load") + .onResourceError(errorHandler::dispatch) + .launch("load") } private fun checkIsNoContent( @@ -267,7 +238,8 @@ class GradeStatisticsPresenter @Inject constructor( items.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0 } GradeStatisticsItem.DataType.POINTS -> { - items.firstOrNull()?.points?.let { points -> points.student == .0 && points.others == .0 } ?: false + items.firstOrNull()?.points?.let { points -> points.student == .0 && points.others == .0 } + ?: false } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 933633dca..b07570cb2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.summary -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter @@ -8,9 +8,6 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeSubject import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -37,56 +34,40 @@ class GradeSummaryPresenter @Inject constructor( } private fun loadData(semesterId: Int, forceRefresh: Boolean) { - Timber.i("Loading grade summary started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) - }.onEach { - Timber.d("Loading grade summary status: ${it.status}, data: ${it.data != null}") - when (it.status) { - Status.LOADING -> { - val items = createGradeSummaryItems(it.data.orEmpty()) - if (items.isNotEmpty()) { - Timber.i("Loading grade summary result: load cached data") - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showEmpty(false) - showContent(true) - updateData(items) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading grade summary result: Success") - val items = createGradeSummaryItems(it.data!!) - view?.run { - showEmpty(items.isEmpty()) - showContent(items.isNotEmpty()) - showErrorView(false) - updateData(items) - } - analytics.logEvent( - "load_data", - "type" to "grade_summary", - "items" to it.data.size - ) - } - Status.ERROR -> { - Timber.i("Loading grade summary result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load grade summary", showData = true) + .mapResourceData { createGradeSummaryItems(it) } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "grade_summary", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showRefresh(false) + showProgress(false) + notifyParentDataLoaded(semesterId) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -153,9 +134,9 @@ class GradeSummaryPresenter @Inject constructor( private fun checkEmpty(gradeSummary: GradeSubject): Boolean { return gradeSummary.run { summary.finalGrade.isBlank() - && summary.predictedGrade.isBlank() - && average == .0 - && points.isBlank() + && summary.predictedGrade.isBlank() + && average == .0 + && points.isBlank() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index d7d5d7cb9..2ac552b41 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -1,21 +1,13 @@ package io.github.wulkanowy.ui.modules.homework -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -89,61 +81,59 @@ class HomeworkPresenter @Inject constructor( flow { val student = studentRepository.getCurrentStudent() emit(semesterRepository.getCurrentSemester(student)) - }.catch { - Timber.i("Loading semester result: An exception occurred") - }.onEach { - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }.launch("holidays") + } + .catch { Timber.i("Loading semester result: An exception occurred") } + .onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + } + .launch("holidays") } private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading homework data started") - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - homeworkRepository.getHomework(student, semester, currentDate, currentDate, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(createHomeworkItem(it.data)) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading homework result: Success") - view?.apply { - updateData(createHomeworkItem(it.data!!)) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "homework", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading homework result: An exception occurred") - errorHandler.dispatch(it.error!!) + homeworkRepository.getHomework( + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh + ) + } + .logResourceStatus("loading homework") + .mapResourceData { createHomeworkItem(it) } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "homework", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -159,9 +149,10 @@ class HomeworkPresenter @Inject constructor( private fun createHomeworkItem(items: List): List> { return items.groupBy { it.date }.toSortedMap().map { (date, exams) -> - listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed().map { exam -> - HomeworkItem(exam, HomeworkItem.ViewType.ITEM) - } + listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed() + .map { exam -> + HomeworkItem(exam, HomeworkItem.ViewType.ITEM) + } }.flatten() } @@ -184,8 +175,10 @@ class HomeworkPresenter @Inject constructor( view?.apply { showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) - updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.sunday.toFormattedString("dd.MM")) + updateNavigationWeek( + "${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM") + ) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt index 3639c2fef..a21f6aef7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt @@ -1,15 +1,16 @@ package io.github.wulkanowy.ui.modules.homework.add -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.toLocalDate -import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.time.LocalDate import javax.inject.Inject @@ -55,7 +56,7 @@ class HomeworkAddPresenter @Inject constructor( } private fun saveHomework(subject: String, teacher: String, date: LocalDate, content: String) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) val entryDate = LocalDate.now() @@ -72,21 +73,15 @@ class HomeworkAddPresenter @Inject constructor( attachments = emptyList(), ).apply { isAddedByUser = true } ) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Homework insert start") - Status.SUCCESS -> { - Timber.i("Homework insert: Success") - view?.run { - showSuccessMessage() - closeDialog() - } - } - Status.ERROR -> { - Timber.i("Homework insert result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("homework insert") + .onResourceSuccess { + view?.run { + showSuccessMessage() + closeDialog() } } - }.launch("add_homework") + .onResourceError(errorHandler::dispatch) + .launch("add_homework") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt index ea9b47a05..e76df6bd0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt @@ -1,15 +1,16 @@ package io.github.wulkanowy.ui.modules.homework.details -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -34,38 +35,26 @@ class HomeworkDetailsPresenter @Inject constructor( } fun deleteHomework(homework: Homework) { - flowWithResource { homeworkRepository.deleteHomework(homework) }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Homework delete start") - Status.SUCCESS -> { - Timber.i("Homework delete: Success") - view?.run { - showMessage(homeworkDeleteSuccess) - closeDialog() - } - } - Status.ERROR -> { - Timber.i("Homework delete result: An exception occurred") - errorHandler.dispatch(it.error!!) + resourceFlow { homeworkRepository.deleteHomework(homework) } + .logResourceStatus("homework delete") + .onResourceSuccess { + view?.run { + showMessage(homeworkDeleteSuccess) + closeDialog() } } - }.launch("delete") + .onResourceError(errorHandler::dispatch) + .launch("delete") } fun toggleDone(homework: Homework) { - flowWithResource { homeworkRepository.toggleDone(homework) }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Homework details update start") - Status.SUCCESS -> { - Timber.i("Homework details update: Success") - view?.updateMarkAsDoneLabel(homework.isDone) - analytics.logEvent("homework_mark_as_done") - } - Status.ERROR -> { - Timber.i("Homework details update result: An exception occurred") - errorHandler.dispatch(it.error!!) - } + resourceFlow { homeworkRepository.toggleDone(homework) } + .logResourceStatus("homework details update") + .onResourceSuccess { + view?.updateMarkAsDoneLabel(homework.isDone) + analytics.logEvent("homework_mark_as_done") } - }.launch("toggle") + .onResourceError(errorHandler::dispatch) + .launch("toggle") } } 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 3543a3041..1b42c6c52 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 @@ -1,15 +1,16 @@ package io.github.wulkanowy.ui.modules.login.advanced -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow 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 -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -129,20 +130,20 @@ class LoginAdvancedPresenter @Inject constructor( fun onSignInClick() { if (!validateCredentials()) return - flowWithResource { getStudentsAppropriatesToLoginType() }.onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Login started") - hideSoftKeyboard() - showProgress(true) - showContent(false) - } - Status.SUCCESS -> { - Timber.i("Login result: Success") - analytics.logEvent( - "registration_form", + resourceFlow { getStudentsAppropriatesToLoginType() } + .logResourceStatus("login") + .onEach { + when (it) { + is Resource.Loading -> view?.run { + hideSoftKeyboard() + showProgress(true) + showContent(false) + } + is Resource.Success -> { + analytics.logEvent( + "registration_form", "success" to true, - "students" to it.data!!.size, + "students" to it.data.size, "error" to "No error" ) val loginData = LoginData( @@ -154,23 +155,22 @@ class LoginAdvancedPresenter @Inject constructor( 0 -> view?.navigateToSymbol(loginData) else -> view?.navigateToStudentSelect(it.data) } + } + is Resource.Error -> { + analytics.logEvent( + "registration_form", + "success" to false, "students" to -1, + "error" to it.error.message.ifNullOrBlank { "No message" } + ) + loginErrorHandler.dispatch(it.error) + } } - Status.ERROR -> { - Timber.i("Login result: An exception occurred") - analytics.logEvent( - "registration_form", - "success" to false, "students" to -1, - "error" to it.error!!.message.ifNullOrBlank { "No message" } - ) - loginErrorHandler.dispatch(it.error) + }.onResourceNotLoading { + view?.apply { + showProgress(false) + showContent(true) } - } - }.afterLoading { - view?.apply { - showProgress(false) - showContent(true) - } - }.launch("login") + }.launch("login") } private suspend fun getStudentsAppropriatesToLoginType(): List { 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 49be6fbb6..b4291ff47 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 @@ -1,16 +1,13 @@ package io.github.wulkanowy.ui.modules.login.form import androidx.core.net.toUri -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* 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 -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank -import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.net.URL import javax.inject.Inject @@ -75,7 +72,7 @@ class LoginFormPresenter @Inject constructor( val username = view?.formUsernameValue.orEmpty().trim() if ("@" in username && "@vulcan" !in username) { - val hosts = view?.getHostsValues().orEmpty().map { it.toUri().host to it }.toMap() + val hosts = view?.getHostsValues().orEmpty().associateBy { it.toUri().host } val usernameHost = username.substringAfter("@") hosts[usernameHost]?.let { @@ -95,54 +92,54 @@ class LoginFormPresenter @Inject constructor( if (!validateCredentials(email, password, host)) return - flowWithResource { + resourceFlow { studentRepository.getStudentsScrapper( email = email, password = password, scrapperBaseUrl = host, symbol = symbol ) - }.onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Login started") + } + .logResourceStatus("login") + .onResourceLoading { + view?.run { hideSoftKeyboard() showProgress(true) showContent(false) } - Status.SUCCESS -> { - Timber.i("Login result: Success") - analytics.logEvent( - "registration_form", - "success" to true, - "students" to it.data!!.size, - "scrapperBaseUrl" to host, - "error" to "No error" - ) - when (it.data.size) { - 0 -> view?.navigateToSymbol(LoginData(email, password, host)) - else -> view?.navigateToStudentSelect(it.data) - } + } + .onResourceSuccess { + when (it.size) { + 0 -> view?.navigateToSymbol(LoginData(email, password, host)) + else -> view?.navigateToStudentSelect(it) } - Status.ERROR -> { - Timber.i("Login result: An exception occurred") - analytics.logEvent( - "registration_form", - "success" to false, - "students" to -1, - "scrapperBaseUrl" to host, - "error" to it.error!!.message.ifNullOrBlank { "No message" }) - loginErrorHandler.dispatch(it.error) - lastError = it.error - view?.showContact(true) + analytics.logEvent( + "registration_form", + "success" to true, + "students" to it.size, + "scrapperBaseUrl" to host, + "error" to "No error" + ) + } + .onResourceNotLoading { + view?.apply { + showProgress(false) + showContent(true) } } - }.afterLoading { - view?.apply { - showProgress(false) - showContent(true) + .onResourceError { + loginErrorHandler.dispatch(it) + lastError = it + view?.showContact(true) + analytics.logEvent( + "registration_form", + "success" to false, + "students" to -1, + "scrapperBaseUrl" to host, + "error" to it.message.ifNullOrBlank { "No message" } + ) } - }.launch("login") + .launch("login") } fun onFaqClick() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt index 271e8a8a0..3d0493012 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt @@ -1,12 +1,12 @@ package io.github.wulkanowy.ui.modules.login.recover -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.repositories.RecoverRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -57,24 +57,28 @@ class LoginRecoverPresenter @Inject constructor( if (!validateInput(username, host)) return - flowWithResource { recoverRepository.getReCaptchaSiteKey(host, symbol.ifBlank { "Default" }) }.onEach { - when (it.status) { - Status.LOADING -> view?.run { + resourceFlow { + recoverRepository.getReCaptchaSiteKey( + host, + symbol.ifBlank { "Default" }) + }.onEach { + when (it) { + is Resource.Loading -> view?.run { hideSoftKeyboard() showRecoverForm(false) showProgress(true) showErrorView(false) showCaptcha(false) } - Status.SUCCESS -> view?.run { - loadReCaptcha(url = it.data!!.first, siteKey = it.data.second) + is Resource.Success -> view?.run { + loadReCaptcha(url = it.data.first, siteKey = it.data.second) showProgress(false) showErrorView(false) showCaptcha(true) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Obtain captcha site key result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) } } }.launch("captcha") @@ -101,26 +105,43 @@ class LoginRecoverPresenter @Inject constructor( val host = view?.recoverHostValue.orEmpty() val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" } - flowWithResource { recoverRepository.sendRecoverRequest(host, symbol, username, reCaptchaResponse) }.onEach { - when (it.status) { - Status.LOADING -> view?.run { + resourceFlow { + recoverRepository.sendRecoverRequest( + host, + symbol, + username, + reCaptchaResponse + ) + }.onEach { + when (it) { + is Resource.Loading -> view?.run { showProgress(true) showRecoverForm(false) showCaptcha(false) } - Status.SUCCESS -> view?.run { + is Resource.Success -> view?.run { showSuccessView(true) - setSuccessTitle(it.data!!.substringBefore(". ")) + setSuccessTitle(it.data.substringBefore(". ")) setSuccessMessage(it.data.substringAfter(". ")) - analytics.logEvent("account_recover", "register" to host, "symbol" to symbol, "success" to true) + analytics.logEvent( + "account_recover", + "register" to host, + "symbol" to symbol, + "success" to true + ) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Send recover request result: An exception occurred") - errorHandler.dispatch(it.error!!) - analytics.logEvent("account_recover", "register" to host, "symbol" to symbol, "success" to false) + errorHandler.dispatch(it.error) + analytics.logEvent( + "account_recover", + "register" to host, + "symbol" to symbol, + "success" to false + ) } } - }.afterLoading { + }.onResourceNotLoading { view?.showProgress(false) }.launch("verified") } 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 71c60e62d..3455b3cf1 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 @@ -1,14 +1,15 @@ package io.github.wulkanowy.ui.modules.login.studentselect -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.logResourceStatus import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow 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 -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -66,16 +67,16 @@ class LoginStudentSelectPresenter @Inject constructor( private fun loadData(studentsWithSemesters: List) { resetSelectedState() - flowWithResource { studentRepository.getSavedStudents(false) }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Login student select students load started") - Status.SUCCESS -> view?.updateData(studentsWithSemesters.map { studentWithSemesters -> - studentWithSemesters to it.data!!.any { item -> + resourceFlow { studentRepository.getSavedStudents(false) }.onEach { + when (it) { + is Resource.Loading -> Timber.d("Login student select students load started") + is Resource.Success -> view?.updateData(studentsWithSemesters.map { studentWithSemesters -> + studentWithSemesters to it.data.any { item -> compareStudents(studentWithSemesters.student, item.student) } }) - Status.ERROR -> { - errorHandler.dispatch(it.error!!) + is Resource.Error -> { + errorHandler.dispatch(it.error) lastError = it.error view?.updateData(studentsWithSemesters.map { student -> student to false }) } @@ -89,29 +90,27 @@ class LoginStudentSelectPresenter @Inject constructor( } private fun registerStudents(studentsWithSemesters: List) { - flowWithResource { studentRepository.saveStudents(studentsWithSemesters) } + resourceFlow { studentRepository.saveStudents(studentsWithSemesters) } + .logResourceStatus("registration") .onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Registration started") + when (it) { + is Resource.Loading -> view?.run { showProgress(true) showContent(false) } - Status.SUCCESS -> { - Timber.i("Registration result: Success") + is Resource.Success -> { syncManager.startOneTimeSyncWorker(quiet = true) view?.openMainView() logRegisterEvent(studentsWithSemesters) } - Status.ERROR -> { - Timber.i("Registration result: An exception occurred ") + is Resource.Error -> { view?.apply { showProgress(false) showContent(true) showContact(true) } lastError = it.error - loginErrorHandler.dispatch(it.error!!) + loginErrorHandler.dispatch(it.error) logRegisterEvent(studentsWithSemesters, it.error) } } 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 7e195893f..691cd4481 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 @@ -1,13 +1,13 @@ package io.github.wulkanowy.ui.modules.login.symbol -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow 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 -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -45,7 +45,7 @@ class LoginSymbolPresenter @Inject constructor( return } - flowWithResource { + resourceFlow { studentRepository.getStudentsScrapper( email = loginData.login, password = loginData.password, @@ -53,15 +53,15 @@ class LoginSymbolPresenter @Inject constructor( symbol = symbol, ) }.onEach { - when (it.status) { - Status.LOADING -> view?.run { + when (it) { + is Resource.Loading -> view?.run { Timber.i("Login with symbol started") hideSoftKeyboard() showProgress(true) showContent(false) } - Status.SUCCESS -> { - when (it.data?.size) { + is Resource.Success -> { + when (it.data.size) { 0 -> { Timber.i("Login with symbol result: Empty student list") view?.run { @@ -77,13 +77,13 @@ class LoginSymbolPresenter @Inject constructor( analytics.logEvent( "registration_symbol", "success" to true, - "students" to it.data!!.size, + "students" to it.data.size, "scrapperBaseUrl" to loginData.baseUrl, "symbol" to symbol, "error" to "No error" ) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Login with symbol result: An exception occurred") analytics.logEvent( "registration_symbol", @@ -91,14 +91,14 @@ class LoginSymbolPresenter @Inject constructor( "students" to -1, "scrapperBaseUrl" to loginData.baseUrl, "symbol" to symbol, - "error" to it.error!!.message.ifNullOrBlank { "No message" } + "error" to it.error.message.ifNullOrBlank { "No message" } ) loginErrorHandler.dispatch(it.error) lastError = it.error view?.showContact(true) } } - }.afterLoading { + }.onResourceNotLoading { view?.apply { showProgress(false) showContent(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt index fd0598d8f..6f5c8e740 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -1,14 +1,11 @@ package io.github.wulkanowy.ui.modules.luckynumber -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -34,47 +31,45 @@ class LuckyNumberPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() luckyNumberRepository.getLuckyNumber(student, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading lucky number started") - Status.SUCCESS -> { - if (it.data != null) { - Timber.i("Loading lucky number result: Success") - view?.apply { - updateData(it.data) - showContent(true) - showEmpty(false) - showErrorView(false) - } - analytics.logEvent( - "load_item", - "type" to "lucky_number", - "number" to it.data.luckyNumber - ) - } else { - Timber.i("Loading lucky number result: No lucky number found") - view?.run { - showContent(false) - showEmpty(true) - showErrorView(false) - } + } + .logResourceStatus("load lucky number") + .onResourceData { + if (it != null) { + view?.apply { + updateData(it) + showContent(true) + showEmpty(false) + showErrorView(false) + } + } else { + view?.run { + showContent(false) + showEmpty(true) + showErrorView(false) } } - Status.ERROR -> { - Timber.i("Loading lucky number result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent( + "load_item", + "type" to "lucky_number", + "number" to it.luckyNumber + ) } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } } - }.launch() + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt index c45cb69a7..fc753950b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt @@ -1,24 +1,12 @@ package io.github.wulkanowy.ui.modules.luckynumber.history -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.previousOrSameSchoolDay -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onEach +import io.github.wulkanowy.utils.* +import kotlinx.coroutines.flow.* import timber.log.Timber import java.time.LocalDate import javax.inject.Inject @@ -52,55 +40,51 @@ class LuckyNumberHistoryPresenter @Inject constructor( flow { val student = studentRepository.getCurrentStudent() emit(semesterRepository.getCurrentSemester(student)) - }.catch { - Timber.i("Loading semester result: An exception occurred") - }.onEach { - currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear) - reloadNavigation() - }.launch("holidays") + } + .catch { Timber.i("Loading semester result: An exception occurred") } + .onEach { + currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear) + reloadNavigation() + } + .launch("holidays") } private fun loadData() { - flowWithResource { + flow { val student = studentRepository.getCurrentStudent() - luckyNumberRepository.getLuckyNumberHistory(student, currentDate.monday, currentDate.sunday) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading lucky number history started") - Status.SUCCESS -> { - if (!it.data?.first().isNullOrEmpty()) { - Timber.i("Loading lucky number result: Success") - view?.apply { - updateData(it.data!!.first()) - showContent(true) - showEmpty(false) - showErrorView(false) - showProgress(false) - } - analytics.logEvent( - "load_items", - "type" to "lucky_number_history", - "numbers" to it.data - ) - } else { - Timber.i("Loading lucky number history result: No lucky numbers found") - view?.run { - showContent(false) - showEmpty(true) - showErrorView(false) - } + emitAll( + luckyNumberRepository.getLuckyNumberHistory( + student = student, + start = currentDate.monday, + end = currentDate.sunday + ) + ) + } + .onEach { + if (!it.isNullOrEmpty()) { + view?.apply { + updateData(it) + showContent(true) + showEmpty(false) + showErrorView(false) + showProgress(false) + } + } else { + view?.run { + showContent(false) + showEmpty(true) + showErrorView(false) + showProgress(false) } } - Status.ERROR -> { - Timber.i("Loading lucky number history result: An exception occurred") - errorHandler.dispatch(it.error!!) - } + + analytics.logEvent( + "load_items", + "type" to "lucky_number_history", + ) } - }.afterLoading { - view?.run { - showProgress(false) - } - }.launch() + .catch { errorHandler.dispatch(it) } + .launchIn(presenterScope) } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -143,8 +127,10 @@ class LuckyNumberHistoryPresenter @Inject constructor( view?.apply { showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) - updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.sunday.toFormattedString("dd.MM")) + updateNavigationWeek( + "${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM") + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index 5b6af69ac..cac648da8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -1,14 +1,14 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getThemeWidgetKey -import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -47,16 +47,15 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( } private fun loadData() { - flowWithResource { studentRepository.getSavedStudents(false) }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Lucky number widget configure students data load") - Status.SUCCESS -> { + resourceFlow { studentRepository.getSavedStudents(false) }.onEach { + when (it) { + is Resource.Loading -> Timber.d("Lucky number widget configure students data load") + is Resource.Success -> { val selectedStudentId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } ?: -1 - when { - it.data!!.isEmpty() -> view?.openLoginView() + it.data.isEmpty() -> view?.openLoginView() it.data.size == 1 -> { selectedStudent = it.data.single().student view?.showThemeDialog() @@ -64,7 +63,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( else -> view?.updateData(it.data, selectedStudentId) } } - Status.ERROR -> errorHandler.dispatch(it.error!!) + is Resource.Error -> errorHandler.dispatch(it.error) } }.launch() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index 2b2d18fa0..e016c07e6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -13,14 +13,17 @@ import android.view.View.VISIBLE import android.widget.RemoteViews import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.utils.PendingIntentCompat -import io.github.wulkanowy.utils.toFirstResult import kotlinx.coroutines.runBlocking import timber.log.Timber import javax.inject.Inject @@ -66,12 +69,16 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) + if (luckyNumber is Resource.Error) { + Timber.e("Error loading lucky number for widget", luckyNumber.error) + } + val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) .apply { setTextViewText( R.id.luckyNumberWidgetNumber, - luckyNumber?.luckyNumber?.toString() ?: "#" + luckyNumber.dataOrNull?.toString() ?: "#" ) setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) } @@ -167,14 +174,17 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { else -> null } - currentStudent?.let { - luckyNumberRepository.getLuckyNumber(it, false).toFirstResult().data + if (currentStudent != null) { + luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false) + .toFirstResult() + } else { + Resource.Success(null) } } catch (e: Exception) { if (e.cause !is NoCurrentStudentException) { Timber.e(e, "An error has occurred in lucky number provider") } - null + Resource.Error(e) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index 821b1e6ff..e01497b9a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -1,9 +1,12 @@ package io.github.wulkanowy.ui.modules.main -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BaseView @@ -16,8 +19,6 @@ import io.github.wulkanowy.ui.modules.message.MessageView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.time.Duration import java.time.Instant @@ -75,20 +76,14 @@ class MainPresenter @Inject constructor( return } - flowWithResource { studentRepository.getSavedStudents(false) } - .onEach { resource -> - when (resource.status) { - Status.LOADING -> Timber.i("Loading student avatar data started") - Status.SUCCESS -> { - studentsWitSemesters = resource.data - showCurrentStudentAvatar() - } - Status.ERROR -> { - Timber.i("Loading student avatar result: An exception occurred") - errorHandler.dispatch(resource.error!!) - } - } - }.launch("avatar") + resourceFlow { studentRepository.getSavedStudents(false) } + .logResourceStatus("load student avatar") + .onResourceSuccess { + studentsWitSemesters = it + showCurrentStudentAvatar() + } + .onResourceError(errorHandler::dispatch) + .launch("avatar") } fun onViewChange(destinationView: BaseView) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index eb33ee6ea..39c337bf2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.message.preview import android.annotation.SuppressLint -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.enums.MessageFolder @@ -10,11 +10,8 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.toFormattedString -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -53,44 +50,43 @@ class MessagePreviewPresenter @Inject constructor( view?.showErrorDetailsDialog(lastError) } - private fun loadData(message: Message) { - flowWithResourceIn { - val student = studentRepository.getStudentById(message.studentId) - messageRepository.getMessage(student, message, true) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading message ${message.messageId} preview started") - Status.SUCCESS -> { - Timber.i("Loading message ${message.messageId} preview result: Success ") - if (it.data != null) { - this@MessagePreviewPresenter.message = it.data.message - this@MessagePreviewPresenter.attachments = it.data.attachments - view?.apply { - setMessageWithAttachment(it.data) - showContent(true) - initOptions() - } - analytics.logEvent( - "load_item", - "type" to "message_preview", - "length" to it.data.message.content.length - ) - } else { - view?.run { - showMessage(messageNotExists) - popView() - } + private fun loadData(messageToLoad: Message) { + flatResourceFlow { + val student = studentRepository.getStudentById(messageToLoad.studentId) + messageRepository.getMessage(student, messageToLoad, true) + } + .logResourceStatus("message ${messageToLoad.messageId} preview") + .onResourceData { + if (it != null) { + message = it.message + attachments = it.attachments + view?.apply { + setMessageWithAttachment(it) + showContent(true) + initOptions() + } + } else { + view?.run { + showMessage(messageNotExists) + popView() } } - Status.ERROR -> { - Timber.i("Loading message ${message.messageId} preview result: An exception occurred ") - retryCallback = { onMessageLoadRetry(message) } - errorHandler.dispatch(it.error!!) + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent( + "load_item", + "type" to "message_preview", + "length" to it.message.content.length + ) } } - }.afterLoading { - view?.showProgress(false) - }.launch() + .onResourceNotLoading { view?.showProgress(false) } + .onResourceError { + retryCallback = { onMessageLoadRetry(messageToLoad) } + errorHandler.dispatch(it) + } + .launch() } fun onReply(): Boolean { @@ -176,28 +172,26 @@ class MessagePreviewPresenter @Inject constructor( showErrorView(false) } - flowWithResource { - val student = studentRepository.getCurrentStudent() - messageRepository.deleteMessage(student, message!!) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Message ${message?.id} delete started") - Status.SUCCESS -> { - Timber.d("Message ${message?.id} delete success") + Timber.i("Delete message ${message?.id}") + + presenterScope.launch { + runCatching { + val student = studentRepository.getCurrentStudent() + messageRepository.deleteMessage(student, message!!) + } + .onFailure { + retryCallback = { onMessageDelete() } + errorHandler.dispatch(it) + } + .onSuccess { view?.run { showMessage(deleteMessageSuccessString) popView() } } - Status.ERROR -> { - Timber.d("Message ${message?.id} delete failed") - retryCallback = { onMessageDelete() } - errorHandler.dispatch(it.error!!) - } - } - }.afterLoading { + view?.showProgress(false) - }.launch("delete") + } } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt index 5e961efc3..e5770955a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -1,25 +1,20 @@ package io.github.wulkanowy.ui.modules.message.send -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.pojos.MessageDraft -import io.github.wulkanowy.data.repositories.MessageRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository -import io.github.wulkanowy.data.repositories.RecipientRepository -import io.github.wulkanowy.data.repositories.ReportingUnitRepository -import io.github.wulkanowy.data.repositories.SemesterRepository -import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.* +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.onEach @@ -55,10 +50,12 @@ class SendMessagePresenter @Inject constructor( setContent(it) } message?.let { - setSubject(when (reply) { - true -> "Re: " - else -> "FW: " - } + message.subject) + setSubject( + when (reply) { + true -> "Re: " + else -> "FW: " + } + message.subject + ) if (preferencesRepository.fillMessageContent || reply != true) { setContent( when (reply) { @@ -67,7 +64,8 @@ class SendMessagePresenter @Inject constructor( } + when (message.sender.isNotEmpty()) { true -> "Od: ${message.sender}\n" false -> "Do: ${message.recipient}\n" - } + "Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${message.content}") + } + "Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${message.content}" + ) } } } @@ -111,7 +109,7 @@ class SendMessagePresenter @Inject constructor( } private fun loadData(message: Message?, reply: Boolean?) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) val unit = reportingUnitRepository.getReportingUnit(student, semester.unitId) @@ -125,58 +123,64 @@ class SendMessagePresenter @Inject constructor( Timber.i("Loading message recipients started") val messageRecipients = when { - message != null && reply == true -> recipientRepository.getMessageRecipients(student, message) + message != null && reply == true -> recipientRepository.getMessageRecipients( + student, + message + ) else -> emptyList() }.let { createChips(it) } - Timber.i("Loaded message recipients to reply result: Success, fetched %d recipients", messageRecipients.size) + Timber.i( + "Loaded message recipients to reply result: Success, fetched %d recipients", + messageRecipients.size + ) Triple(unit, recipients, messageRecipients) - }.onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Loading recipients started") - showProgress(true) - showContent(false) - } - Status.SUCCESS -> it.data!!.let { (reportingUnit, recipientChips, selectedRecipientChips) -> - view?.run { - if (reportingUnit != null) { - setReportingUnit(reportingUnit) - setRecipients(recipientChips) - if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(selectedRecipientChips) - showContent(true) - } else { - Timber.i("Loading recipients result: Can't find the reporting unit") - view?.showEmpty(true) + } + .logResourceStatus("load recipients") + .onEach { + when (it) { + is Resource.Loading -> view?.run { + showProgress(true) + showContent(false) + } + is Resource.Success -> it.data.let { (reportingUnit, recipientChips, selectedRecipientChips) -> + view?.run { + if (reportingUnit != null) { + setReportingUnit(reportingUnit) + setRecipients(recipientChips) + if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients( + selectedRecipientChips + ) + showContent(true) + } else { + Timber.i("Loading recipients result: Can't find the reporting unit") + view?.showEmpty(true) + } } } + is Resource.Error -> { + view?.showContent(true) + errorHandler.dispatch(it.error) + } } - Status.ERROR -> { - Timber.i("Loading recipients result: An exception occurred") - view?.showContent(true) - errorHandler.dispatch(it.error!!) - } - } - }.afterLoading { - view?.run { showProgress(false) } - }.launch() + }.onResourceNotLoading { + view?.run { showProgress(false) } + }.launch() } private fun sendMessage(subject: String, content: String, recipients: List) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() messageRepository.sendMessage(student, subject, content, recipients) - }.onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Sending message started") + }.logResourceStatus("sending message").onEach { + when (it) { + is Resource.Loading -> view?.run { showSoftInput(false) showContent(false) showProgress(true) showActionBar(false) } - Status.SUCCESS -> { - Timber.i("Sending message result: Success") + is Resource.Success -> { view?.clearDraft() view?.run { showMessage(messageSuccess) @@ -184,14 +188,13 @@ class SendMessagePresenter @Inject constructor( } analytics.logEvent("send_message", "recipients" to recipients.size) } - Status.ERROR -> { - Timber.i("Sending message result: An exception occurred") + is Resource.Error -> { view?.run { showContent(true) showProgress(false) showActionBar(true) } - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) } } }.launch("send") @@ -259,7 +262,8 @@ class SendMessagePresenter @Inject constructor( } fun getRecipientsNames(): String { - return messageRepository.draftMessage?.recipients.orEmpty().joinToString { it.recipient.name } + return messageRepository.draftMessage?.recipients.orEmpty() + .joinToString { it.recipient.name } } fun clearDraft() { @@ -267,6 +271,7 @@ class SendMessagePresenter @Inject constructor( Timber.i("Draft cleared!") } - fun getMessageBackupContent(recipients: String) = if (recipients.isEmpty()) view?.getMessageBackupDialogString() + fun getMessageBackupContent(recipients: String) = + if (recipients.isEmpty()) view?.getMessageBackupDialogString() else view?.getMessageBackupDialogStringWithRecipients(recipients) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index f70a1bab7..57055a644 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.message.tab -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.repositories.MessageRepository @@ -9,17 +9,10 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import me.xdrop.fuzzywuzzy.FuzzySearch import timber.log.Timber @@ -107,64 +100,75 @@ class MessageTabPresenter @Inject constructor( ) { Timber.i("Loading $folder message data started") - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) messageRepository.getMessages(student, semester, folder, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showErrorView(false) - showRefresh(true) - showProgress(false) - showContent(true) - messages = it.data - val filteredData = getFilteredData( + } + .logResourceStatus("load $folder message") + .onEach { + when (it) { + is Resource.Intermediate -> { + if (it.data.isNotEmpty()) { + view?.run { + enableSwipe(true) + showErrorView(false) + showRefresh(true) + showProgress(false) + showContent(true) + messages = it.data + val filteredData = getFilteredData( + lastSearchQuery, + onlyUnread, + onlyWithAttachments + ) + val messageItems = filteredData.map { message -> + MessageTabDataItem.MessageItem(message) + } + val messageItemsWithHeader = + listOf(MessageTabDataItem.Header) + messageItems + + updateData( + messageItemsWithHeader, + folder.id == MessageFolder.SENT.id + ) + notifyParentDataLoaded() + } + } + } + is Resource.Success -> { + messages = it.data + updateData( + getFilteredData( lastSearchQuery, onlyUnread, onlyWithAttachments ) - val messageItems = filteredData.map { message -> - MessageTabDataItem.MessageItem(message) - } - val messageItemsWithHeader = - listOf(MessageTabDataItem.Header) + messageItems - - updateData(messageItemsWithHeader, folder.id == MessageFolder.SENT.id) - notifyParentDataLoaded() - } + ) + analytics.logEvent( + "load_data", + "type" to "messages", + "items" to it.data.size, + "folder" to folder.name + ) } - } - Status.SUCCESS -> { - Timber.i("Loading $folder message result: Success") - messages = it.data!! - updateData(getFilteredData(lastSearchQuery, onlyUnread, onlyWithAttachments)) - analytics.logEvent( - "load_data", - "type" to "messages", - "items" to it.data.size, - "folder" to folder.name - ) - } - Status.ERROR -> { - Timber.i("Loading $folder message result: An exception occurred") - errorHandler.dispatch(it.error!!) + else -> {} } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() + .onResourceNotLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } } - }.catch { - errorHandler.dispatch(it) - view?.notifyParentDataLoaded() - }.launch() + .onResourceError(errorHandler::dispatch) + .catch { + errorHandler.dispatch(it) + view?.notifyParentDataLoaded() + } + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt index 53049891a..36a720e53 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.mobiledevice -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.repositories.MobileDeviceRepository import io.github.wulkanowy.data.repositories.SemesterRepository @@ -8,10 +8,6 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -52,49 +48,39 @@ class MobileDevicePresenter @Inject constructor( private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading mobile devices data started") - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) mobileDeviceRepository.getDevices(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(it.data) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading mobile devices result: Success") - view?.run { - updateData(it.data!!) - showContent(it.data.isNotEmpty()) - showEmpty(it.data.isEmpty()) - showErrorView(false) - } - analytics.logEvent( - "load_data", - "type" to "devices", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading mobile devices result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load mobile devices data") + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "devices", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -128,25 +114,19 @@ class MobileDevicePresenter @Inject constructor( } fun onUnregisterConfirmed(device: MobileDevice) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) mobileDeviceRepository.unregisterDevice(student, semester, device) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Unregister device started") - Status.SUCCESS -> { - Timber.i("Unregister device result: Success") - view?.run { - showProgress(false) - enableSwipe(true) - } - } - Status.ERROR -> { - Timber.i("Unregister device result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("unregister device") + .onResourceSuccess { + view?.run { + showProgress(false) + enableSwipe(true) } } - }.launch("unregister") + .onResourceError(errorHandler::dispatch) + .launch("unregister") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt index 5e7110ee5..875b73ad7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt @@ -1,15 +1,12 @@ package io.github.wulkanowy.ui.modules.mobiledevice.token -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.repositories.MobileDeviceRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -29,29 +26,29 @@ class MobileDeviceTokenPresenter @Inject constructor( } private fun loadData() { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) mobileDeviceRepository.getToken(student, semester) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Mobile device registration data started") - Status.SUCCESS -> { - Timber.i("Mobile device registration result: Success") - view?.run { - updateData(it.data!!) - showContent() - } - analytics.logEvent("device_register", "symbol" to it.data!!.token.substring(0, 3)) - } - Status.ERROR -> { - Timber.i("Mobile device registration result: An exception occurred") - view?.closeDialog() - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load mobile device registration") + .onResourceData { + view?.run { + updateData(it) + showContent() } } - }.afterLoading { - view?.hideLoading() - }.launch() + .onResourceSuccess { + analytics.logEvent( + "device_register", + "symbol" to it.token.substring(0, 3) + ) + } + .onResourceNotLoading { view?.hideLoading() } + .onResourceError { + view?.closeDialog() + errorHandler.dispatch(it) + } + .launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index 10a391820..440565e11 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.note -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.repositories.NoteRepository import io.github.wulkanowy.data.repositories.SemesterRepository @@ -8,9 +8,6 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -51,51 +48,40 @@ class NotePresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading note data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) noteRepository.getNotes(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(it.data.sortedByDescending { item -> item.date }) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading note result: Success") - view?.apply { - updateData(it.data!!.sortedByDescending { item -> item.date }) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "note", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading note result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load note data") + .mapResourceData { it.sortedByDescending { note -> note.date } } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "note", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -122,14 +108,14 @@ class NotePresenter @Inject constructor( } private fun updateNote(note: Note) { - flowWithResource { noteRepository.updateNote(note) } + resourceFlow { noteRepository.updateNote(note) } .onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to update note ${note.id}") - Status.SUCCESS -> Timber.i("Update note result: Success") - Status.ERROR -> { + when (it) { + is Resource.Loading -> Timber.i("Attempt to update note ${note.id}") + is Resource.Success -> Timber.i("Update note result: Success") + is Resource.Error -> { Timber.i("Update note result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt index ac8c273ea..262398b8a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt @@ -1,16 +1,13 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.school -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.repositories.SchoolRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -65,46 +62,48 @@ class SchoolPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) schoolRepository.getSchoolInfo(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading school info started") - Status.SUCCESS -> if (it.data != null) { - Timber.i("Loading teachers result: Success") + } + .logResourceStatus("load school info") + .onResourceData { + if (it != null) { view?.run { - address = it.data.address.ifBlank { null } - contact = it.data.contact.ifBlank { null } - updateData(it.data) + address = it.address.ifBlank { null } + contact = it.contact.ifBlank { null } + updateData(it) showContent(true) showEmpty(false) showErrorView(false) } - analytics.logEvent("load_item", "type" to "school") } else view?.run { Timber.i("Loading school result: No school info found") showContent(!isViewEmpty) showEmpty(isViewEmpty) showErrorView(false) } - Status.ERROR -> { - Timber.i("Loading school result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent("load_item", "type" to "school") } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } } - }.catch { - errorHandler.dispatch(it) - view?.notifyParentDataLoaded() - }.launch() + .onResourceError(errorHandler::dispatch) + .catch { + errorHandler.dispatch(it) + view?.notifyParentDataLoaded() + } + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt index bd46ff0b2..e2af05c92 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt @@ -1,16 +1,13 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TeacherRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -53,43 +50,41 @@ class TeacherPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) teacherRepository.getTeachers(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading teachers data started") - Status.SUCCESS -> { - Timber.i("Loading teachers result: Success") - view?.run { - updateData(it.data!!.filter { item -> item.name.isNotBlank() }) - showContent(it.data.isNotEmpty()) - showEmpty(it.data.isEmpty()) - showErrorView(false) - } - analytics.logEvent( - "load_data", - "type" to "teachers", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading teachers result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load teachers data") + .onResourceData { + view?.run { + updateData(it.filter { item -> item.name.isNotBlank() }) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + showErrorView(false) } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "teachers", + "items" to it.size + ) } - }.catch { - errorHandler.dispatch(it) - view?.notifyParentDataLoaded() - }.launch() + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } + } + .onResourceError(errorHandler::dispatch) + .catch { + errorHandler.dispatch(it) + view?.notifyParentDataLoaded() + } + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt index 62c93198d..f77a88335 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt @@ -1,15 +1,12 @@ package io.github.wulkanowy.ui.modules.schoolannouncement -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -52,50 +49,37 @@ class SchoolAnnouncementPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading School announcement data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showErrorView(false) - showProgress(false) - showContent(true) - updateData(it.data) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading School announcement result: Success") - view?.apply { - updateData(it.data!!) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_school_announcement", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading School announcement result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load school announcement") + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceSuccess { + analytics.logEvent( + "load_school_announcement", + "items" to it.size + ) } - }.launch() + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch("load_data") } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt index 80798b11e..083b590b2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.studentinfo -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.StudentInfo import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentInfoRepository @@ -8,10 +8,7 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.getCurrentOrLast -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -72,51 +69,50 @@ class StudentInfoPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - flowWithResourceIn { + flatResourceFlow { val semester = studentWithSemesters.semesters.getCurrentOrLast() studentInfoRepository.getStudentInfo( student = studentWithSemesters.student, semester = semester, forceRefresh = forceRefresh ) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading student info $infoType started") - Status.SUCCESS -> { - val isFamily = infoType == StudentInfoView.Type.FAMILY - val isFirstGuardianEmpty = it.data?.firstGuardian == null - val isSecondGuardianEmpty = it.data?.secondGuardian == null - - if (it.data != null && !(isFamily && isFirstGuardianEmpty && isSecondGuardianEmpty)) { - Timber.i("Loading student info $infoType result: Success") - showCorrectData(it.data) - view?.run { - showContent(true) - showEmpty(false) - showErrorView(false) - } - analytics.logEvent("load_item", "type" to "student_info") - } else { - Timber.i("Loading student info $infoType result: No student or family info found") - view?.run { - showContent(!isViewEmpty) - showEmpty(isViewEmpty) - showErrorView(false) - } + } + .logResourceStatus("load student info $infoType") + .onResourceData { + val isFamily = infoType == StudentInfoView.Type.FAMILY + val isFirstGuardianEmpty = it?.firstGuardian == null + val isSecondGuardianEmpty = it?.secondGuardian == null + if (it != null && !(isFamily && isFirstGuardianEmpty && isSecondGuardianEmpty)) { + Timber.i("Loading student info $infoType result: Success") + showCorrectData(it) + view?.run { + showContent(true) + showEmpty(false) + showErrorView(false) + } + } else { + Timber.i("Loading student info $infoType result: No student or family info found") + view?.run { + showContent(!isViewEmpty) + showEmpty(isViewEmpty) + showErrorView(false) } } - Status.ERROR -> { - Timber.i("Loading student info $infoType result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent("load_item", "type" to "student_info") } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } } - }.launch() + .onResourceError(errorHandler::dispatch) + .launch() } private fun showCorrectData(studentInfo: StudentInfo) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index ec3ef7b02..dc6c89213 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.timetable -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.enums.TimetableMode import io.github.wulkanowy.data.repositories.PreferencesRepository @@ -123,57 +123,47 @@ class TimetablePresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading timetable data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) timetableRepository.getTimetable( - student, semester, currentDate, currentDate, forceRefresh + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh, + timetableType = TimetableRepository.TimetableType.NORMAL ) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data?.lessons.isNullOrEmpty()) { - view?.run { - updateData(it.data!!.lessons) - enableSwipe(true) - showRefresh(true) - showErrorView(false) - showProgress(false) - showContent(true) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading timetable result: Success") - view?.apply { - updateData(it.data!!.lessons) - showEmpty(it.data.lessons.isEmpty()) - setDayHeaderMessage(it.data.headers.singleOrNull { header -> - header.date == currentDate - }?.content) - showErrorView(false) - showContent(it.data.lessons.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "timetable", - "items" to it.data!!.lessons.size - ) - } - Status.ERROR -> { - Timber.i("Loading timetable result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load timetable data") + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.lessons.isNotEmpty()) + showEmpty(it.lessons.isEmpty()) + updateData(it.lessons) + setDayHeaderMessage(it.headers.singleOrNull { header -> header.date == currentDate }?.content) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "timetable", + "items" to it.lessons.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun updateData(lessons: List) { 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 742a8d592..d0a01b38c 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 @@ -1,23 +1,14 @@ package io.github.wulkanowy.ui.modules.timetable.additional import android.annotation.SuppressLint -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* 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.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -137,39 +128,44 @@ class AdditionalLessonsPresenter @Inject constructor( private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { currentDate = date - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - timetableRepository.getTimetable(student, semester, date, date, forceRefresh, true) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading additional lessons data started") - Status.SUCCESS -> { - Timber.i("Loading additional lessons lessons result: Success") - view?.apply { - updateData(it.data!!.additional.sortedBy { item -> item.start }) - showEmpty(it.data.additional.isEmpty()) - showErrorView(false) - showContent(it.data.additional.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "additional_lessons", - "items" to it.data!!.additional.size - ) - } - Status.ERROR -> { - Timber.i("Loading additional lessons result: An exception occurred") - errorHandler.dispatch(it.error!!) + timetableRepository.getTimetable( + student = student, + semester = semester, + start = date, + end = date, + forceRefresh = forceRefresh, + refreshAdditional = true, + timetableType = TimetableRepository.TimetableType.ADDITIONAL + ) + } + .logResourceStatus("load additional lessons") + .onResourceData { + view?.apply { + updateData(it.additional.sortedBy { item -> item.start }) + showEmpty(it.additional.isEmpty()) + showErrorView(false) + showContent(it.additional.isNotEmpty()) } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "additional_lessons", + "items" to it.additional.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index b75b42f8b..16c51fd2e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -1,22 +1,13 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.annotation.SuppressLint -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.repositories.CompletedLessonsRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -111,51 +102,46 @@ class CompletedLessonsPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading completed lessons data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - completedLessonsRepository.getCompletedLessons(student, semester, currentDate, currentDate, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(it.data.sortedBy { item -> item.number }) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading completed lessons lessons result: Success") - view?.apply { - updateData(it.data!!.sortedBy { item -> item.number }) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "completed_lessons", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading completed lessons result: An exception occurred") - completedLessonsErrorHandler.dispatch(it.error!!) + completedLessonsRepository.getCompletedLessons( + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh + ) + } + .logResourceStatus("load completed lessons") + .mapResourceData { it.sortedBy { lesson -> lesson.number } } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "completed_lessons", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt index 2a40c8e4a..dc2a7c6c7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt @@ -1,14 +1,14 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey -import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -56,16 +56,15 @@ class TimetableWidgetConfigurePresenter @Inject constructor( } private fun loadData() { - flowWithResource { studentRepository.getSavedStudents(false) }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Timetable widget configure students data load") - Status.SUCCESS -> { + resourceFlow { studentRepository.getSavedStudents(false) }.onEach { + when (it) { + is Resource.Loading -> Timber.d("Timetable widget configure students data load") + is Resource.Success -> { val selectedStudentId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } ?: -1 - when { - it.data!!.isEmpty() -> view?.openLoginView() + it.data.isEmpty() -> view?.openLoginView() it.data.size == 1 && !isFromProvider -> { selectedStudent = it.data.single().student view?.showThemeDialog() @@ -73,7 +72,7 @@ class TimetableWidgetConfigurePresenter @Inject constructor( else -> view?.updateData(it.data, selectedStudentId) } } - Status.ERROR -> errorHandler.dispatch(it.error!!) + is Resource.Error -> errorHandler.dispatch(it.error) } }.launch() } 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 18eefc5da..664086bca 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 @@ -12,6 +12,7 @@ import android.widget.AdapterView.INVALID_POSITION import android.widget.RemoteViews import android.widget.RemoteViewsService import io.github.wulkanowy.R +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.enums.TimetableMode @@ -19,12 +20,12 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository 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.toFirstResult 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 @@ -118,7 +119,7 @@ class TimetableWidgetFactory( val semester = semesterRepository.getCurrentSemester(student) timetableRepository.getTimetable(student, semester, date, date, false) - .toFirstResult().data?.lessons.orEmpty() + .toFirstResult().dataOrNull?.lessons.orEmpty() .sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) .filter { if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) { diff --git a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt deleted file mode 100644 index 5dd289677..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt +++ /dev/null @@ -1,96 +0,0 @@ -package io.github.wulkanowy.utils - -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.takeWhile -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock - -inline fun networkBoundResource( - mutex: Mutex = Mutex(), - showSavedOnLoading: Boolean = true, - crossinline query: () -> Flow, - crossinline fetch: suspend (ResultType) -> RequestType, - crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, - crossinline onFetchFailed: (Throwable) -> Unit = { }, - crossinline shouldFetch: (ResultType) -> Boolean = { true }, - crossinline filterResult: (ResultType) -> ResultType = { it } -) = flow { - emit(Resource.loading()) - - val data = query().first() - emitAll(if (shouldFetch(data)) { - if (showSavedOnLoading) emit(Resource.loading(filterResult(data))) - - try { - val newData = fetch(data) - mutex.withLock { saveFetchResult(query().first(), newData) } - query().map { Resource.success(filterResult(it)) } - } catch (throwable: Throwable) { - onFetchFailed(throwable) - query().map { Resource.error(throwable, filterResult(it)) } - } - } else { - query().map { Resource.success(filterResult(it)) } - }) -} - -@JvmName("networkBoundResourceWithMap") -inline fun networkBoundResource( - mutex: Mutex = Mutex(), - showSavedOnLoading: Boolean = true, - crossinline query: () -> Flow, - crossinline fetch: suspend (ResultType) -> RequestType, - crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, - crossinline onFetchFailed: (Throwable) -> Unit = { }, - crossinline shouldFetch: (ResultType) -> Boolean = { true }, - crossinline mapResult: (ResultType) -> T -) = flow { - emit(Resource.loading()) - - val data = query().first() - emitAll(if (shouldFetch(data)) { - if (showSavedOnLoading) emit(Resource.loading(mapResult(data))) - - try { - val newData = fetch(data) - mutex.withLock { saveFetchResult(query().first(), newData) } - query().map { Resource.success(mapResult(it)) } - } catch (throwable: Throwable) { - onFetchFailed(throwable) - query().map { Resource.error(throwable, mapResult(it)) } - } - } else { - query().map { Resource.success(mapResult(it)) } - }) -} - -fun flowWithResource(block: suspend () -> T) = flow { - emit(Resource.loading()) - emit(Resource.success(block())) -}.catch { emit(Resource.error(it)) } - -@OptIn(FlowPreview::class) -fun flowWithResourceIn(block: suspend () -> Flow>) = flow { - emit(Resource.loading()) - emitAll(block().filter { it.status != Status.LOADING || (it.status == Status.LOADING && it.data != null) }) -}.catch { emit(Resource.error(it)) } - -fun Flow>.afterLoading(callback: () -> Unit) = onEach { - if (it.status != Status.LOADING) callback() -} - -suspend fun Flow>.toFirstResult() = filter { it.status != Status.LOADING }.first() - -suspend fun Flow>.waitForResult() = - takeWhile { it.status == Status.LOADING }.collect() diff --git a/app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt b/app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt similarity index 96% rename from app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt rename to app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt index 57045a29d..ea846a57b 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.utils +package io.github.wulkanowy.data import io.mockk.* import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -13,7 +13,7 @@ import org.junit.Test import kotlin.test.assertEquals @OptIn(ExperimentalCoroutinesApi::class) -class FlowUtilsKtTest { +class ResourceTest { private val testScope = TestScope(UnconfinedTestDispatcher()) @@ -41,6 +41,7 @@ class FlowUtilsKtTest { // first networkBoundResource( + isResultEmpty = { false }, showSavedOnLoading = false, query = { repo.query() }, fetch = { @@ -55,6 +56,7 @@ class FlowUtilsKtTest { // second networkBoundResource( + isResultEmpty = { false }, showSavedOnLoading = false, query = { repo.query() }, fetch = { @@ -120,6 +122,7 @@ class FlowUtilsKtTest { // first networkBoundResource( + isResultEmpty = { false }, mutex = saveResultMutex, showSavedOnLoading = false, query = { repo.query() }, @@ -138,6 +141,7 @@ class FlowUtilsKtTest { // second networkBoundResource( + isResultEmpty = { false }, mutex = saveResultMutex, showSavedOnLoading = false, query = { repo.query() }, diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt index f3c7fba77..7d22f7265 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt @@ -1,20 +1,17 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.AttendanceDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity 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 @@ -73,8 +70,8 @@ class AttendanceRepositoryTest { val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getAttendance(startDate, endDate, 1) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } @@ -97,8 +94,8 @@ class AttendanceRepositoryTest { val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getAttendance(startDate, endDate, 1) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { @@ -125,8 +122,8 @@ class AttendanceRepositoryTest { val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getAttendance(startDate, endDate, 1) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt index fa54522a8..c28ea304b 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt @@ -1,20 +1,17 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.CompletedLessonsDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity 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 @@ -73,8 +70,8 @@ class CompletedLessonsRepositoryTest { val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) } @@ -97,8 +94,8 @@ class CompletedLessonsRepositoryTest { val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } coVerify { @@ -125,8 +122,8 @@ class CompletedLessonsRepositoryTest { val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt index 8bf4deee3..e3790662e 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt @@ -1,20 +1,17 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.ExamDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity 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 @@ -74,8 +71,8 @@ class ExamRemoteTest { val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getExams(startDate, realEndDate, 1) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.insertAll(match { it.isEmpty() }) } @@ -98,8 +95,8 @@ class ExamRemoteTest { val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getExams(startDate, realEndDate, 1) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { @@ -126,8 +123,8 @@ class ExamRemoteTest { val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getExams(startDate, realEndDate, 1) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.insertAll(match { it.isEmpty() }) } 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 f7968bc41..e8d0b6c8f 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 @@ -1,13 +1,15 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity 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.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK @@ -88,8 +90,8 @@ class GradeRepositoryTest { } // verify - assertEquals(null, res.error) - assertEquals(4, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(4, res.dataOrNull?.first?.size) coVerify { gradeDb.insertAll(withArg { assertEquals(4, it.size) @@ -142,8 +144,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(4, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(4, res.dataOrNull?.first?.size) coVerify { gradeDb.insertAll(withArg { assertEquals(3, it.size) @@ -184,8 +186,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.first?.size) coVerify { gradeDb.insertAll(match { it.isEmpty() }) } coVerify { gradeDb.deleteAll(match { it.size == 1 }) } // ... here } @@ -214,8 +216,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(3, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(3, res.dataOrNull?.first?.size) coVerify { gradeDb.insertAll(match { it.size == 1 }) } // ... here coVerify { gradeDb.deleteAll(match { it.isEmpty() }) } } @@ -240,8 +242,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(3, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(3, res.dataOrNull?.first?.size) } @Test @@ -263,8 +265,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(0, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(0, res.dataOrNull?.first?.size) } private fun createGradeApi(value: Int, weight: Double, date: LocalDate, desc: String) = diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt index 6221b6989..8e2f7c6ef 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt @@ -1,16 +1,18 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.toFirstResult import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK @@ -81,11 +83,11 @@ class GradeStatisticsRepositoryTest { forceRefresh = true, ).toFirstResult() } - val items = res.data.orEmpty() + val items = res.dataOrNull.orEmpty() // verify - assertEquals(null, res.error) - assertEquals(2 + 1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2 + 1, res.dataOrNull?.size) assertEquals("", items[0].partial?.studentAverage) assertEquals("", items[1].partial?.studentAverage) assertEquals("", items[2].partial?.studentAverage) @@ -119,11 +121,11 @@ class GradeStatisticsRepositoryTest { forceRefresh = true, ).toFirstResult() } - val items = res.data.orEmpty() + val items = res.dataOrNull.orEmpty() // verify - assertEquals(null, res.error) - assertEquals(2 + 1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2 + 1, res.dataOrNull?.size) assertEquals("3,00", items[0].partial?.studentAverage) assertEquals("1.0", items[1].partial?.studentAverage) assertEquals("5.0", items[2].partial?.studentAverage) diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt index a89aad35c..3225c3bd2 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt @@ -1,17 +1,15 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.LuckyNumberDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk -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.* 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 @@ -58,8 +56,8 @@ class LuckyNumberRemoteTest { val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(luckyNumber.number, res.data?.luckyNumber) + assertEquals(null, res.errorOrNull) + assertEquals(luckyNumber.number, res.dataOrNull?.luckyNumber) coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { luckyNumberDb.load(1, date) } coVerify(exactly = 0) { luckyNumberDb.insertAll(any()) } @@ -82,8 +80,8 @@ class LuckyNumberRemoteTest { val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(luckyNumber.number, res.data?.luckyNumber) + assertEquals(null, res.errorOrNull) + assertEquals(luckyNumber.number, res.dataOrNull?.luckyNumber) coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { luckyNumberDb.load(1, date) } coVerify { @@ -112,8 +110,8 @@ class LuckyNumberRemoteTest { val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(luckyNumber.number, res.data?.luckyNumber) + assertEquals(null, res.errorOrNull) + assertEquals(luckyNumber.number, res.dataOrNull?.luckyNumber) coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { luckyNumberDb.load(1, date) } coVerify { 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 f21fc1780..2a5d2e2b4 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 @@ -1,13 +1,15 @@ package io.github.wulkanowy.data.repositories import android.content.Context -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.data.errorOrNull +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk @@ -15,7 +17,8 @@ import io.github.wulkanowy.sdk.pojo.Folder 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.github.wulkanowy.utils.Status +import io.github.wulkanowy.utils.status import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK @@ -102,7 +105,7 @@ class MessageRepositoryTest { folder = MessageFolder.RECEIVED, forceRefresh = true, notify = true, // all new messages will be marked as not notified - ).toFirstResult().data.orEmpty() + ).toFirstResult().dataOrNull.orEmpty() coVerify(exactly = 1) { messageDb.deleteAll(emptyList()) } coVerify(exactly = 1) { messageDb.insertAll(emptyList()) } @@ -133,7 +136,7 @@ class MessageRepositoryTest { folder = MessageFolder.RECEIVED, forceRefresh = true, notify = false, - ).toFirstResult().data.orEmpty() + ).toFirstResult().dataOrNull.orEmpty() coVerify(exactly = 1) { messageDb.deleteAll(withArg { checkEquals(emptyList()) }) } coVerify { @@ -165,9 +168,9 @@ class MessageRepositoryTest { val res = runBlocking { repository.getMessage(student, testMessage).toFirstResult() } - assertEquals(null, res.error) + assertEquals(null, res.errorOrNull) assertEquals(Status.SUCCESS, res.status) - assertEquals("Test", res.data!!.message.content) + assertEquals("Test", res.dataOrNull!!.message.content) } @Test @@ -197,9 +200,9 @@ class MessageRepositoryTest { val res = runBlocking { repository.getMessage(student, testMessage).toFirstResult() } - assertEquals(null, res.error) + assertEquals(null, res.errorOrNull) assertEquals(Status.SUCCESS, res.status) - assertEquals("Test", res.data!!.message.content) + assertEquals("Test", res.dataOrNull!!.message.content) coVerify { messageDb.updateAll(listOf(testMessageWithContent)) } } 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 c5a7756ff..b9a958d43 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 @@ -1,21 +1,18 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.MobileDeviceDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Device 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 @@ -69,8 +66,8 @@ class MobileDeviceRepositoryTest { val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } // verify - Assert.assertEquals(null, res.error) - Assert.assertEquals(2, res.data?.size) + Assert.assertEquals(null, res.errorOrNull) + Assert.assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getRegisteredDevices() } coVerify { mobileDeviceDb.loadAll(1) } coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) } @@ -93,8 +90,8 @@ class MobileDeviceRepositoryTest { val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } // verify - Assert.assertEquals(null, res.error) - Assert.assertEquals(2, res.data?.size) + Assert.assertEquals(null, res.errorOrNull) + Assert.assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getRegisteredDevices() } coVerify { mobileDeviceDb.loadAll(1) } coVerify { @@ -121,8 +118,8 @@ class MobileDeviceRepositoryTest { val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } // verify - Assert.assertEquals(null, res.error) - Assert.assertEquals(1, res.data?.size) + Assert.assertEquals(null, res.errorOrNull) + Assert.assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getRegisteredDevices() } coVerify { mobileDeviceDb.loadAll(1) } coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) } 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 adb4f33a1..e56aaa5d0 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 @@ -1,24 +1,21 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull 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.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.TimetableFull import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper 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 @@ -96,8 +93,8 @@ class TimetableRepositoryTest { val res = runBlocking { timetableRepository.getTimetable(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.lessons?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull!!.lessons.size) coVerify { sdk.getTimetableFull(startDate, endDate) } coVerify { timetableDb.loadAll(1, 1, startDate, endDate) } coVerify { timetableDb.insertAll(match { it.isEmpty() }) } 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 5e8c4c119..a6ecdc26b 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 @@ -1,16 +1,18 @@ package io.github.wulkanowy.ui.modules.grade import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary 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.data.repositories.SemesterRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.flowWithResource +import io.github.wulkanowy.utils.Status +import io.github.wulkanowy.utils.status import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every @@ -29,7 +31,7 @@ import java.time.LocalDate.of class GradeAverageProviderTest { - private suspend fun Flow>.getResult() = toList()[1].data!! + private suspend fun Flow>.getResult() = toList()[1].dataOrNull!! @MockK lateinit var preferencesRepository: PreferencesRepository @@ -144,7 +146,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { noWeightGrades to noWeightGradesSummary } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { noWeightGrades to noWeightGradesSummary } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -156,7 +164,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.isOptionalArithmeticAverage } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { noWeightGrades to noWeightGradesArithmeticSummary } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { noWeightGrades to noWeightGradesArithmeticSummary } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -170,27 +184,27 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(secondGradeWithModifier to secondSummariesWithModifier)) - emit(Resource.success(secondGradeWithModifier to secondSummariesWithModifier)) + emit(Resource.Loading()) + emit(Resource.Intermediate(secondGradeWithModifier to secondSummariesWithModifier)) + emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.LOADING, status) - assertEquals(1, data!!.size) + assertEquals(1, dataOrNull?.size) } with(items[2]) { assertEquals(Status.SUCCESS, status) - assertEquals(1, data!!.size) + assertEquals(1, dataOrNull?.size) } - assertEquals(3.5, items[1].data?.single { it.subject == "Język polski" }!!.average, .0) // from details and after set custom plus/minus + assertEquals(3.5, items[1].dataOrNull?.single { it.subject == "Język polski" }?.average ?: 0.0, .0) // from details and after set custom plus/minus } @Test @@ -201,27 +215,27 @@ class GradeAverageProviderTest { coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flow { - emit(Resource.loading()) + emit(Resource.Loading()) delay(1000) - emit(Resource.success(secondGradeWithModifier to secondSummariesWithModifier)) + emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) } coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flow { - emit(Resource.loading()) - emit(Resource.success(secondGradeWithModifier to secondSummariesWithModifier)) + emit(Resource.Loading()) + emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, false).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.SUCCESS, status) - assertEquals(1, data!!.size) + assertEquals(1, dataOrNull?.size) } - assertEquals(3.5, items[1].data?.single { it.subject == "Język polski" }!!.average, .0) // from details and after set custom plus/minus + assertEquals(3.5, items[1].dataOrNull?.single { it.subject == "Język polski" }?.average ?: 0.0, .0) // from details and after set custom plus/minus } @Test @@ -230,12 +244,26 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } - coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flowWithResource { - listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)) to listOf(getSummary(semesters[2].semesterId, "Język polski", 2.5)) + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + false + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns resourceFlow { + listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)) to listOf( + getSummary(semesters[2].semesterId, "Język polski", 2.5) + ) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, false).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + false + ).getResult() + } assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) } @@ -246,10 +274,28 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } - coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flowWithResource { emptyList() to listOf(getSummary(24, "Język polski", .0))} + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + false + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + false + ) + } returns resourceFlow { emptyList() to listOf(getSummary(24, "Język polski", .0)) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, false).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + false + ).getResult() + } assertEquals(3.49, items.single { it.subject == "Język polski" }.average, .0) } @@ -260,10 +306,28 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { emptyList() to emptyList() } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { emptyList() to emptyList() } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { emptyList() to emptyList() } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { emptyList() to emptyList() } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(0, items.size) } @@ -274,7 +338,13 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -292,7 +362,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -310,7 +386,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -328,7 +410,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -340,7 +428,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -354,7 +448,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -368,7 +468,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId, true).getResult() } @@ -384,29 +490,29 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(firstGrades to firstSummaries)) - emit(Resource.success(firstGrades to firstSummaries)) + emit(Resource.Loading()) + emit(Resource.Intermediate(firstGrades to firstSummaries)) + emit(Resource.Success(firstGrades to firstSummaries)) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId, true).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.LOADING, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } with(items[2]) { assertEquals(Status.SUCCESS, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } - assertEquals(2, items[2].data!!.size) - assertEquals(3.5, items[2].data!!.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5 - assertEquals(3.5, items[2].data!!.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5 + assertEquals(2, items[2].dataOrNull?.size) + assertEquals(3.5, items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, .0) // (from summary): 3,5 + assertEquals(3.5, items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, .0) // (from summary): 3,5 } @Test @@ -414,13 +520,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.isOptionalArithmeticAverage } returns false - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { firstGrades to listOf( getSummary(22, "Matematyka", 3.0), getSummary(22, "Fizyka", 3.5) ) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { secondGrades to listOf( getSummary(22, "Matematyka", 3.5), getSummary(22, "Fizyka", 4.0) @@ -440,46 +546,62 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageForceCalc } returns false coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(firstGrades to listOf( - getSummary(22, "Matematyka", 3.0), - getSummary(22, "Fizyka", 3.5) - ))) - emit(Resource.success(firstGrades to listOf( - getSummary(22, "Matematyka", 3.0), - getSummary(22, "Fizyka", 3.5) - ))) + emit(Resource.Loading()) + emit( + Resource.Intermediate( + firstGrades to listOf( + getSummary(22, "Matematyka", 3.0), + getSummary(22, "Fizyka", 3.5) + ) + ) + ) + emit( + Resource.Success( + firstGrades to listOf( + getSummary(22, "Matematyka", 3.0), + getSummary(22, "Fizyka", 3.5) + ) + ) + ) } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(secondGrades to listOf( - getSummary(22, "Matematyka", 3.5), - getSummary(22, "Fizyka", 4.0) - ))) - emit(Resource.success(secondGrades to listOf( - getSummary(22, "Matematyka", 3.5), - getSummary(22, "Fizyka", 4.0) - ))) + emit(Resource.Loading()) + emit( + Resource.Intermediate( + secondGrades to listOf( + getSummary(22, "Matematyka", 3.5), + getSummary(22, "Fizyka", 4.0) + ) + ) + ) + emit( + Resource.Success( + secondGrades to listOf( + getSummary(22, "Matematyka", 3.5), + getSummary(22, "Fizyka", 4.0) + ) + ) + ) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.LOADING, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } with(items[2]) { assertEquals(Status.SUCCESS, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } - assertEquals(2, items[2].data!!.size) - assertEquals(3.25, items[2].data!!.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25 - assertEquals(3.75, items[2].data!!.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75 + assertEquals(2, items[2].dataOrNull?.size) + assertEquals(3.25, items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25 + assertEquals(3.75, items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75 } @Test @@ -487,8 +609,14 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { secondGrades to listOf( getSummary(22, "Matematyka", 1.1), getSummary(22, "Fizyka", 7.26) @@ -527,7 +655,7 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns flowWithResource { + } returns resourceFlow { firstGrades to listOf( getSummary(22, "Matematyka", .0), getSummary(22, "Fizyka", .0) @@ -539,7 +667,7 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns flowWithResource { + } returns resourceFlow { secondGrades to listOf( getSummary(22, "Matematyka", .0), getSummary(22, "Fizyka", .0) @@ -566,40 +694,48 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(firstGrades to firstSummaries)) - emit(Resource.success(firstGrades to firstSummaries)) + emit(Resource.Loading()) + emit(Resource.Intermediate(firstGrades to firstSummaries)) + emit(Resource.Success(firstGrades to firstSummaries)) } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(secondGrades to listOf( - getSummary(22, "Matematyka", 1.1), - getSummary(22, "Fizyka", 7.26) - ))) - emit(Resource.success(secondGrades to listOf( - getSummary(22, "Matematyka", 1.1), - getSummary(22, "Fizyka", 7.26) - ))) + emit(Resource.Loading()) + emit( + Resource.Intermediate( + secondGrades to listOf( + getSummary(22, "Matematyka", 1.1), + getSummary(22, "Fizyka", 7.26) + ) + ) + ) + emit( + Resource.Success( + secondGrades to listOf( + getSummary(22, "Matematyka", 1.1), + getSummary(22, "Fizyka", 7.26) + ) + ) + ) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.LOADING, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } with(items[2]) { assertEquals(Status.SUCCESS, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } - assertEquals(2, items[2].data!!.size) - assertEquals(3.0, items[2].data!!.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 - assertEquals(3.25, items[2].data!!.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals(2, items[2].dataOrNull?.size) + assertEquals(3.0, items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, .0) // (from details): 3,5 + 2,5 → 3,0 + assertEquals(3.25, items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, .0) // (from details): 3,5 + 3,0 → 3,25 } @Test @@ -614,14 +750,14 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns flowWithResource { firstGrades to emptyList() } + } returns resourceFlow { firstGrades to emptyList() } coEvery { gradeRepository.getGrades( student, semesters[2], true ) - } returns flowWithResource { secondGrades to emptyList() } + } returns resourceFlow { secondGrades to emptyList() } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -646,14 +782,40 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to emptyList() } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to emptyList() } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to emptyList() } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to emptyList() } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 - assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals( + 3.0, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from details): 3,5 + 2,5 → 3,0 + assertEquals( + 3.25, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // (from details): 3,5 + 3,0 → 3,25 } @Test @@ -662,12 +824,12 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { firstGrades to listOf( getSummary(22, "Matematyka", 4.0) ) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { secondGrades to listOf( getSummary(23, "Matematyka", 3.0) ) @@ -686,14 +848,40 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries.dropLast(1) } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries.dropLast(1) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4 - assertEquals(3.05, items.single { it.subject == "Fizyka" }.average, .0) // 3,1 (from summary) + 3,0 (from details) → 3,05 + assertEquals( + 3.4, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from summaries): 3,9 + 2,9 → 3,4 + assertEquals( + 3.05, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // 3,1 (from summary) + 3,0 (from details) → 3,05 } @Test @@ -702,14 +890,40 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries.dropLast(1) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4 - assertEquals(3.45, items.single { it.subject == "Fizyka" }.average, .0) // 3,5 (from details) + 3,4 (from summary) → 3,45 + assertEquals( + 3.4, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from summaries): 3,9 + 2,9 → 3,4 + assertEquals( + 3.45, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // 3,5 (from details) + 3,4 (from summary) → 3,45 } @Test @@ -718,14 +932,40 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries.dropLast(1) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 - assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals( + 3.0, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from details): 3,5 + 2,5 → 3,0 + assertEquals( + 3.25, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // (from details): 3,5 + 3,0 → 3,25 } @Test @@ -734,7 +974,7 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS every { preferencesRepository.isOptionalArithmeticAverage } returns false - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { listOf( getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0), @@ -746,7 +986,7 @@ class GradeAverageProviderTest { getGrade(22, "Fizyka", 6.0, weight = 2.0) ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { listOf( getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 2.0), @@ -765,7 +1005,7 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { listOf( getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0), @@ -777,7 +1017,7 @@ class GradeAverageProviderTest { getGrade(22, "Fizyka", 6.0, weight = 2.0) ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { listOf( getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 2.0), @@ -802,7 +1042,7 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { listOf( getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0), @@ -814,7 +1054,7 @@ class GradeAverageProviderTest { getGrade(22, "Fizyka", 6.0, weight = 2.0) ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { listOf( getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 2.0), @@ -839,7 +1079,7 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { listOf( getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0), @@ -851,7 +1091,7 @@ class GradeAverageProviderTest { getGrade(22, "Fizyka", 6.0, weight = 2.0) ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { listOf( getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 2.0), @@ -881,9 +1121,9 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns - flowWithResource { firstGrades to firstSummaries } + resourceFlow { firstGrades to firstSummaries } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns - flowWithResource { listOf() to firstSummaries } + resourceFlow { listOf() to firstSummaries } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( diff --git a/app/src/test/java/io/github/wulkanowy/utils/ResourceUtils.kt b/app/src/test/java/io/github/wulkanowy/utils/ResourceUtils.kt new file mode 100644 index 000000000..60df1db09 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/utils/ResourceUtils.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.data.Resource + +enum class Status { + LOADING, SUCCESS, ERROR +} + +val Resource.status + get() = when (this) { + is Resource.Error -> Status.ERROR + is Resource.Loading -> Status.LOADING + is Resource.Success -> Status.SUCCESS + } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a0f7639f7..d7e66b5c6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882ed..1b6c78733 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" From c572a91b38e7ca0ac541f2d3f19799408fffef9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 13:53:42 +0000 Subject: [PATCH 082/105] Bump agconnect-crash from 1.6.4.300 to 1.6.5.200 (#1811) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7ae210675..c7e42fde8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:20.6.0' hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.300' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.4.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.200' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 63380d3e12bfafc4a6275616fb52b9733966d62f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 13:53:57 +0000 Subject: [PATCH 083/105] Bump agcp from 1.6.4.300 to 1.6.5.200 (#1812) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6b8d14ed9..742d39569 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.1.2' 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.4.300' + classpath 'com.huawei.agconnect:agcp:1.6.5.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.3" From 2131e892ad705d48626c4e4312a0d7321674be76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 28 Mar 2022 19:30:20 +0200 Subject: [PATCH 084/105] Add option to select multiple messages to delete (#1780) --- .../data/repositories/MessageRepository.kt | 29 +- .../modules/attendance/AttendanceFragment.kt | 12 +- .../ui/modules/message/MessageFragment.kt | 45 ++- .../ui/modules/message/MessagePresenter.kt | 14 +- .../ui/modules/message/MessageView.kt | 6 +- .../message/preview/MessagePreviewFragment.kt | 2 +- .../modules/message/tab/MessageTabAdapter.kt | 154 +++++----- .../modules/message/tab/MessageTabDataItem.kt | 21 +- .../modules/message/tab/MessageTabFragment.kt | 123 ++++++-- .../message/tab/MessageTabPresenter.kt | 274 ++++++++++++------ .../ui/modules/message/tab/MessageTabView.kt | 22 +- .../wulkanowy/ui/modules/more/MoreFragment.kt | 5 + .../res/drawable/ic_message_select_all.xml | 17 ++ .../res/drawable/ic_message_unselect_all.xml | 12 + .../res/layout/fragment_message_preview.xml | 4 +- app/src/main/res/layout/item_message.xml | 19 +- .../res/menu/action_menu_message_preview.xml | 2 +- ...excuse.xml => context_menu_attendance.xml} | 0 .../res/menu/context_menu_message_tab.xml | 18 ++ app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values/strings.xml | 16 +- 26 files changed, 569 insertions(+), 238 deletions(-) create mode 100644 app/src/main/res/drawable/ic_message_select_all.xml create mode 100644 app/src/main/res/drawable/ic_message_unselect_all.xml rename app/src/main/res/menu/{context_menu_excuse.xml => context_menu_attendance.xml} (100%) create mode 100644 app/src/main/res/menu/context_menu_message_tab.xml diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt index 8d6fd772a..05fb97657 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt @@ -103,7 +103,7 @@ class MessageRepository @Inject constructor( message: Message, markAsRead: Boolean = false, ): Flow> = networkBoundResource( - isResultEmpty = { it == null }, + isResultEmpty = { it?.message?.content.isNullOrBlank() }, shouldFetch = { checkNotNull(it) { "This message no longer exist!" } Timber.d("Message content in db empty: ${it.message.content.isEmpty()}") @@ -151,20 +151,27 @@ class MessageRepository @Inject constructor( recipients = recipients.mapFromEntities() ) - suspend fun deleteMessage(student: Student, message: Message) { - val isDeleted = sdk.init(student).deleteMessages( - messages = listOf(message.messageId), message.folderId - ) + suspend fun deleteMessages(student: Student, messages: List) { + val folderId = messages.first().folderId + val isDeleted = sdk.init(student) + .deleteMessages(messages = messages.map { it.messageId }, folderId = folderId) - if (message.folderId != MessageFolder.TRASHED.id && isDeleted) { - val deletedMessage = message.copy(folderId = MessageFolder.TRASHED.id).apply { - id = message.id - content = message.content + if (folderId != MessageFolder.TRASHED.id && isDeleted) { + val deletedMessages = messages.map { + it.copy(folderId = MessageFolder.TRASHED.id) + .apply { + id = it.id + content = it.content + } } - messagesDb.updateAll(listOf(deletedMessage)) - } else messagesDb.deleteAll(listOf(message)) + + messagesDb.updateAll(deletedMessages) + } else messagesDb.deleteAll(messages) } + suspend fun deleteMessage(student: Student, message: Message) = + deleteMessages(student, listOf(message)) + var draftMessage: MessageDraft? get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft)) ?.let { json.decodeFromString(it) } 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 84af1ca32..6354b5e04 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 @@ -2,14 +2,8 @@ package io.github.wulkanowy.ui.modules.attendance import android.content.DialogInterface.BUTTON_POSITIVE import android.os.Bundle -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE +import android.view.* +import android.view.View.* import androidx.appcompat.app.AlertDialog import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible @@ -68,7 +62,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag private val actionModeCallback = object : ActionMode.Callback { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { val inflater = mode.menuInflater - inflater.inflate(R.menu.context_menu_excuse, menu) + inflater.inflate(R.menu.context_menu_attendance, menu) return true } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt index acf3133d9..4607793c9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt @@ -4,12 +4,14 @@ import android.os.Bundle import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.core.view.updateMargins import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED -import io.github.wulkanowy.data.enums.MessageFolder.SENT -import io.github.wulkanowy.data.enums.MessageFolder.TRASHED +import io.github.wulkanowy.data.enums.MessageFolder.* import io.github.wulkanowy.databinding.FragmentMessageBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter @@ -78,7 +80,6 @@ class MessageFragment : BaseFragment(R.layout.fragment_m } binding.messageTabLayout.elevation = requireContext().dpToPx(4f) - binding.openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() } } @@ -93,12 +94,37 @@ class MessageFragment : BaseFragment(R.layout.fragment_m binding.messageProgress.visibility = if (show) VISIBLE else INVISIBLE } + override fun showNewMessage(show: Boolean) { + binding.openSendMessageButton.run { + if (show) show() else hide() + } + } + + override fun showTabLayout(show: Boolean) { + binding.messageTabLayout.isVisible = show + + with(binding.messageViewPager) { + isUserInputEnabled = show + updateLayoutParams { + updateMargins(top = if (show) requireContext().dpToPx(48f).toInt() else 0) + } + } + } + + fun onChildFragmentShowActionMode(show: Boolean) { + presenter.onChildViewShowActionMode(show) + } + fun onChildFragmentLoaded() { presenter.onChildViewLoaded() } - override fun notifyChildMessageDeleted(tabId: Int) { - (pagerAdapter.getFragmentInstance(tabId) as? MessageTabFragment)?.onParentDeleteMessage() + fun onChildFragmentShowNewMessage(show: Boolean) { + presenter.onChildViewShowNewMessage(show) + } + + fun onFragmentChanged() { + presenter.onFragmentChanged() } override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) { @@ -106,6 +132,13 @@ class MessageFragment : BaseFragment(R.layout.fragment_m ?.onParentLoadData(forceRefresh) } + override fun notifyChildrenFinishActionMode() { + repeat(3) { + (pagerAdapter.getFragmentInstance(it) as? MessageTabFragment) + ?.onParentFinishActionMode() + } + } + override fun openSendMessage() { context?.let { it.startActivity(SendMessageActivity.getStartIntent(it)) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt index 9e19517bd..68bdc4b7c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.message import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -24,6 +23,7 @@ class MessagePresenter @Inject constructor( fun onPageSelected(index: Int) { loadChild(index) + view?.notifyChildrenFinishActionMode() } private fun loadData() { @@ -35,6 +35,10 @@ class MessagePresenter @Inject constructor( view?.notifyChildLoadData(index, forceRefresh) } + fun onFragmentChanged() { + view?.notifyChildrenFinishActionMode() + } + fun onChildViewLoaded() { view?.apply { showContent(true) @@ -42,6 +46,14 @@ class MessagePresenter @Inject constructor( } } + fun onChildViewShowNewMessage(show: Boolean) { + view?.showNewMessage(show) + } + + fun onChildViewShowActionMode(show: Boolean) { + view?.showTabLayout(!show) + } + fun onSendMessageButtonClicked() { view?.openSendMessage() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt index 2aa4d78ec..e0cc5098c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt @@ -12,9 +12,13 @@ interface MessageView : BaseView { fun showProgress(show: Boolean) + fun showNewMessage(show: Boolean) + + fun showTabLayout(show: Boolean) + fun notifyChildLoadData(index: Int, forceRefresh: Boolean) - fun notifyChildMessageDeleted(tabId: Int) + fun notifyChildrenFinishActionMode() fun openSendMessage() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt index e1cc2e374..860ecc571 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt @@ -142,7 +142,7 @@ class MessagePreviewFragment : } override fun setNotDeletedOptionsLabels() { - menuDeleteButton?.setTitle(R.string.message_move_to_bin) + menuDeleteButton?.setTitle(R.string.message_move_to_trash) } override fun showErrorView(show: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index 571cc6d55..af0923b94 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -2,15 +2,12 @@ package io.github.wulkanowy.ui.modules.message.tab import android.graphics.Typeface import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import android.widget.CompoundButton import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.NO_POSITION import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.databinding.ItemMessageBinding import io.github.wulkanowy.databinding.ItemMessageChipsBinding @@ -20,118 +17,141 @@ import javax.inject.Inject class MessageTabAdapter @Inject constructor() : RecyclerView.Adapter() { - enum class ViewType { HEADER, ITEM } + var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit = { _, _ -> } - var onItemClickListener: (Message, position: Int) -> Unit = { _, _ -> } - var onHeaderClickListener: (chip: CompoundButton, isChecked: Boolean) -> Unit = { _, _ -> } + var onLongItemClickListener: (MessageTabDataItem.MessageItem) -> Unit = {} + + var onHeaderClickListener: (CompoundButton, Boolean) -> Unit = { _, _ -> } var onChangesDetectedListener = {} private var items = mutableListOf() - private var onlyUnread: Boolean? = null - private var onlyWithAttachments = false - fun setDataItems( - data: List, - onlyUnread: Boolean?, - onlyWithAttachments: Boolean - ) { - if (items.size != data.size) onChangesDetectedListener() + fun submitData(data: List) { + val originalMessagesSize = items.count { it.viewType == MessageItemViewType.MESSAGE } + val newMessagesSize = data.count { it.viewType == MessageItemViewType.MESSAGE } + + if (originalMessagesSize != newMessagesSize) onChangesDetectedListener() + val diffResult = DiffUtil.calculateDiff(MessageTabDiffUtil(items, data)) items = data.toMutableList() - this.onlyUnread = onlyUnread - this.onlyWithAttachments = onlyWithAttachments + diffResult.dispatchUpdatesTo(this) } - override fun getItemViewType(position: Int): Int { - return when (position) { - 0 -> ViewType.HEADER.ordinal - else -> ViewType.ITEM.ordinal - } - } + override fun getItemViewType(position: Int) = items[position].viewType.ordinal override fun getItemCount() = items.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) - return when (viewType) { - ViewType.ITEM.ordinal -> ItemViewHolder( + + return when (MessageItemViewType.values()[viewType]) { + MessageItemViewType.MESSAGE -> ItemViewHolder( ItemMessageBinding.inflate(inflater, parent, false) ) - ViewType.HEADER.ordinal -> HeaderViewHolder( + MessageItemViewType.FILTERS -> HeaderViewHolder( ItemMessageChipsBinding.inflate(inflater, parent, false) ) - else -> throw IllegalStateException() } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { - is ItemViewHolder -> { - val item = (items[position] as MessageTabDataItem.MessageItem).message + is ItemViewHolder -> bindItemViewHolder(holder, position) + is HeaderViewHolder -> bindHeaderViewHolder(holder, position) + } + } - with(holder.binding) { - val style = if (item.unread) Typeface.BOLD else Typeface.NORMAL + private fun bindHeaderViewHolder(holder: HeaderViewHolder, position: Int) { + val item = items[position] as MessageTabDataItem.FilterHeader - messageItemAuthor.run { - text = - if (item.folderId == MessageFolder.SENT.id) item.recipient else item.sender - setTypeface(null, style) - } - messageItemSubject.run { - text = - if (item.subject.isNotBlank()) item.subject else context.getString(R.string.message_no_subject) - setTypeface(null, style) - } - messageItemDate.run { - text = item.date.toFormattedString() - setTypeface(null, style) - } - messageItemAttachmentIcon.visibility = - if (item.hasAttachments) View.VISIBLE else View.GONE + with(holder.binding) { + if (item.onlyUnread == null) { + chipUnread.isVisible = false + } else { + chipUnread.isVisible = true + chipUnread.isChecked = item.onlyUnread + chipUnread.setOnCheckedChangeListener(onHeaderClickListener) + } + chipUnread.isEnabled = item.isEnabled + chipAttachments.isEnabled = item.isEnabled + chipAttachments.isChecked = item.onlyWithAttachments + chipAttachments.setOnCheckedChangeListener(onHeaderClickListener) + } + } - root.setOnClickListener { - holder.bindingAdapterPosition.let { - if (it != NO_POSITION) onItemClickListener(item, it) - } + private fun bindItemViewHolder(holder: ItemViewHolder, position: Int) { + val item = (items[position] as MessageTabDataItem.MessageItem) + val message = item.message + + with(holder.binding) { + val style = if (message.unread) Typeface.BOLD else Typeface.NORMAL + + messageItemAuthor.run { + text = if (message.folderId == MessageFolder.SENT.id) { + message.recipient + } else { + message.sender + } + setTypeface(null, style) + } + messageItemSubject.run { + text = message.subject.ifBlank { context.getString(R.string.message_no_subject) } + setTypeface(null, style) + } + messageItemDate.run { + text = message.date.toFormattedString() + setTypeface(null, style) + } + messageItemAttachmentIcon.isVisible = message.hasAttachments + + root.setOnClickListener { + holder.bindingAdapterPosition.let { + if (it != RecyclerView.NO_POSITION) { + onItemClickListener(item, it) } } } - is HeaderViewHolder -> { - with(holder.binding) { - if (onlyUnread == null) chipUnread.isVisible = false - else { - chipUnread.isVisible = true - chipUnread.isChecked = onlyUnread!! - chipUnread.setOnCheckedChangeListener(onHeaderClickListener) - } - chipAttachments.isChecked = onlyWithAttachments - chipAttachments.setOnCheckedChangeListener(onHeaderClickListener) - } + + root.setOnLongClickListener { + onLongItemClickListener(item) + return@setOnLongClickListener true + } + + with(messageItemCheckbox) { + isChecked = item.isSelected + isVisible = item.isActionMode } } } class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root) + class HeaderViewHolder(val binding: ItemMessageChipsBinding) : RecyclerView.ViewHolder(binding.root) private class MessageTabDiffUtil( private val old: List, private val new: List - ) : - DiffUtil.Callback() { + ) : DiffUtil.Callback() { + override fun getOldListSize(): Int = old.size override fun getNewListSize(): Int = new.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return old[oldItemPosition].id == new[newItemPosition].id + val oldItem = old[oldItemPosition] + val newItem = new[newItemPosition] + + return if (oldItem is MessageTabDataItem.MessageItem && newItem is MessageTabDataItem.MessageItem) { + oldItem.message.id == newItem.message.id + } else { + oldItem.viewType == newItem.viewType + } } - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return old[oldItemPosition] == new[newItemPosition] - } + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + old[oldItemPosition] == new[newItemPosition] } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt index 4f51a936f..634dfc0e7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt @@ -2,14 +2,19 @@ package io.github.wulkanowy.ui.modules.message.tab import io.github.wulkanowy.data.db.entities.Message -sealed class MessageTabDataItem { - data class MessageItem(val message: Message) : MessageTabDataItem() { - override val id = message.id - } +sealed class MessageTabDataItem(val viewType: MessageItemViewType) { - object Header : MessageTabDataItem() { - override val id = Long.MIN_VALUE - } + data class MessageItem( + val message: Message, + val isSelected: Boolean, + val isActionMode: Boolean + ) : MessageTabDataItem(MessageItemViewType.MESSAGE) - abstract val id: Long + data class FilterHeader( + val onlyUnread: Boolean?, + val onlyWithAttachments: Boolean, + val isEnabled: Boolean + ) : MessageTabDataItem(MessageItemViewType.FILTERS) } + +enum class MessageItemViewType { FILTERS, MESSAGE } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt index 54ee74eb1..654b0e226 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt @@ -3,12 +3,13 @@ package io.github.wulkanowy.ui.modules.message.tab import android.os.Bundle import android.view.Menu import android.view.MenuInflater +import android.view.MenuItem import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE +import android.view.View.* import android.widget.CompoundButton +import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView +import androidx.core.view.updatePadding import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -20,7 +21,9 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.hideSoftInput import javax.inject.Inject @AndroidEntryPoint @@ -31,9 +34,10 @@ class MessageTabFragment : BaseFragment(R.layout.frag lateinit var presenter: MessageTabPresenter @Inject - lateinit var tabAdapter: MessageTabAdapter + lateinit var messageTabAdapter: MessageTabAdapter companion object { + const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id" fun newInstance(folder: MessageFolder): MessageTabFragment { @@ -46,11 +50,38 @@ class MessageTabFragment : BaseFragment(R.layout.frag } override val isViewEmpty - get() = tabAdapter.itemCount == 0 + get() = messageTabAdapter.itemCount == 0 - override var onlyUnread: Boolean? = false + private var actionMode: ActionMode? = null - override var onlyWithAttachments = false + private val actionModeCallback = object : ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + val inflater = mode.menuInflater + inflater.inflate(R.menu.context_menu_message_tab, menu) + return true + } + + override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + if (presenter.folder == MessageFolder.TRASHED) { + val menuItem = menu.findItem(R.id.messageTabContextMenuDelete) + menuItem.setTitle(R.string.message_delete_forever) + } + return presenter.onPrepareActionMode() + } + + override fun onDestroyActionMode(mode: ActionMode) { + presenter.onDestroyActionMode() + actionMode = null + } + + override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean { + when (menu.itemId) { + R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete() + R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll() + } + return true + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -69,24 +100,25 @@ class MessageTabFragment : BaseFragment(R.layout.frag } override fun initView() { - with(tabAdapter) { + with(messageTabAdapter) { onItemClickListener = presenter::onMessageItemSelected + onLongItemClickListener = presenter::onMessageItemLongSelected onHeaderClickListener = ::onChipChecked onChangesDetectedListener = ::resetListPosition } with(binding.messageTabRecycler) { layoutManager = LinearLayoutManager(context) - adapter = tabAdapter + adapter = messageTabAdapter addItemDecoration(DividerItemDecoration(context, false)) + itemAnimator = null } + with(binding) { messageTabSwipe.setOnRefreshListener(presenter::onSwipeRefresh) messageTabSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) messageTabSwipe.setProgressBackgroundColorSchemeColor( - requireContext().getThemeAttrColor( - R.attr.colorSwipeRefresh - ) + requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh) ) messageTabErrorRetry.setOnClickListener { presenter.onRetry() } messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } @@ -109,9 +141,28 @@ class MessageTabFragment : BaseFragment(R.layout.frag }) } - override fun updateData(data: List, hide: Boolean) { - if (hide) onlyUnread = null - tabAdapter.setDataItems(data, onlyUnread, onlyWithAttachments) + override fun updateData(data: List) { + messageTabAdapter.submitData(data) + } + + override fun updateActionModeTitle(selectedMessagesSize: Int) { + actionMode?.title = resources.getQuantityString( + R.plurals.message_selected_messages_count, + selectedMessagesSize, + selectedMessagesSize + ) + } + + override fun updateSelectAllMenu(isAllSelected: Boolean) { + val menuItem = actionMode?.menu?.findItem(R.id.messageTabContextMenuSelectAll) ?: return + + if (isAllSelected) { + menuItem.setTitle(R.string.message_unselect_all) + menuItem.setIcon(R.drawable.ic_message_unselect_all) + } else { + menuItem.setTitle(R.string.message_select_all) + menuItem.setIcon(R.drawable.ic_message_select_all) + } } override fun showProgress(show: Boolean) { @@ -146,6 +197,14 @@ class MessageTabFragment : BaseFragment(R.layout.frag binding.messageTabSwipe.isRefreshing = show } + override fun showMessagesDeleted() { + showMessage(getString(R.string.message_messages_deleted)) + } + + override fun notifyParentShowNewMessage(show: Boolean) { + (parentFragment as? MessageFragment)?.onChildFragmentShowNewMessage(show) + } + override fun openMessage(message: Message) { (activity as? MainActivity)?.pushView(MessagePreviewFragment.newInstance(message)) } @@ -154,12 +213,16 @@ class MessageTabFragment : BaseFragment(R.layout.frag (parentFragment as? MessageFragment)?.onChildFragmentLoaded() } - fun onParentLoadData( - forceRefresh: Boolean, - onlyUnread: Boolean? = this.onlyUnread, - onlyWithAttachments: Boolean = this.onlyWithAttachments - ) { - presenter.onParentViewLoadData(forceRefresh, onlyUnread, onlyWithAttachments) + override fun notifyParentShowActionMode(show: Boolean) { + (parentFragment as? MessageFragment)?.onChildFragmentShowActionMode(show) + } + + fun onParentLoadData(forceRefresh: Boolean) { + presenter.onParentViewLoadData(forceRefresh) + } + + fun onParentFinishActionMode() { + presenter.onParentFinishActionMode() } private fun onChipChecked(chip: CompoundButton, isChecked: Boolean) { @@ -169,8 +232,22 @@ class MessageTabFragment : BaseFragment(R.layout.frag } } - fun onParentDeleteMessage() { - presenter.onDeleteMessage() + override fun showActionMode(show: Boolean) { + if (show) { + actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback) + } else { + actionMode?.finish() + } + } + + override fun showRecyclerBottomPadding(show: Boolean) { + binding.messageTabRecycler.updatePadding( + bottom = if (show) requireContext().dpToPx(64f).toInt() else 0 + ) + } + + override fun hideKeyboard() { + activity?.hideSoftInput() } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 57055a644..870b6433e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -12,7 +12,10 @@ import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import me.xdrop.fuzzywuzzy.FuzzySearch import timber.log.Timber @@ -37,6 +40,14 @@ class MessageTabPresenter @Inject constructor( private val searchChannel = Channel() + private val messagesToDelete = mutableSetOf() + + private var onlyUnread: Boolean? = false + + private var onlyWithAttachments = false + + private var isActionMode = false + fun onAttachView(view: MessageTabView, folder: MessageFolder) { super.onAttachView(view) view.initView() @@ -47,14 +58,14 @@ class MessageTabPresenter @Inject constructor( fun onSwipeRefresh() { Timber.i("Force refreshing the $folder message") - view?.run { onParentViewLoadData(true, onlyUnread, onlyWithAttachments) } + view?.run { loadData(true) } } fun onRetry() { view?.run { showErrorView(false) showProgress(true) - loadData(true, onlyUnread == true, onlyWithAttachments) + loadData(true) } } @@ -62,42 +73,135 @@ class MessageTabPresenter @Inject constructor( view?.showErrorDetailsDialog(lastError) } - fun onDeleteMessage() { - view?.run { loadData(true, onlyUnread == true, onlyWithAttachments) } + fun onParentViewLoadData(forceRefresh: Boolean) { + loadData(forceRefresh) } - fun onParentViewLoadData( - forceRefresh: Boolean, - onlyUnread: Boolean? = view?.onlyUnread, - onlyWithAttachments: Boolean = view?.onlyWithAttachments == true - ) { - loadData(forceRefresh, onlyUnread == true, onlyWithAttachments) + fun onParentFinishActionMode() { + view?.showActionMode(false) } - fun onMessageItemSelected(message: Message, position: Int) { - Timber.i("Select message ${message.id} item (position: $position)") - view?.openMessage(message) + fun onDestroyActionMode() { + isActionMode = false + messagesToDelete.clear() + updateDataInView() + + view?.run { + enableSwipe(true) + notifyParentShowNewMessage(true) + notifyParentShowActionMode(false) + showRecyclerBottomPadding(true) + } + } + + fun onPrepareActionMode(): Boolean { + isActionMode = true + messagesToDelete.clear() + updateDataInView() + + view?.apply { + enableSwipe(false) + notifyParentShowNewMessage(false) + notifyParentShowActionMode(true) + showRecyclerBottomPadding(false) + hideKeyboard() + } + return true + } + + fun onActionModeSelectDelete() { + Timber.i("Delete ${messagesToDelete.size} messages)") + val messageList = messagesToDelete.toList() + + presenterScope.launch { + view?.run { + showProgress(true) + showContent(false) + showActionMode(false) + } + + runCatching { + val student = studentRepository.getCurrentStudent(true) + messageRepository.deleteMessages(student, messageList) + } + .onFailure(errorHandler::dispatch) + .onSuccess { view?.showMessagesDeleted() } + } + } + + fun onActionModeSelectCheckAll() { + val messagesToSelect = getFilteredData() + val isAllSelected = messagesToDelete.containsAll(messagesToSelect) + + if (isAllSelected) { + messagesToDelete.clear() + view?.showActionMode(false) + } else { + messagesToDelete.addAll(messagesToSelect) + updateDataInView() + } + + view?.run { + updateSelectAllMenu(!isAllSelected) + updateActionModeTitle(messagesToDelete.size) + } + } + + fun onMessageItemLongSelected(messageItem: MessageTabDataItem.MessageItem) { + if (!isActionMode) { + view?.showActionMode(true) + + messagesToDelete.add(messageItem.message) + + view?.updateActionModeTitle(messagesToDelete.size) + updateDataInView() + } + } + + fun onMessageItemSelected(messageItem: MessageTabDataItem.MessageItem, position: Int) { + Timber.i("Select message ${messageItem.message.id} item (position: $position)") + + if (!isActionMode) { + view?.run { + showActionMode(false) + openMessage(messageItem.message) + } + } else { + if (!messageItem.isSelected) { + messagesToDelete.add(messageItem.message) + } else { + messagesToDelete.remove(messageItem.message) + } + + if (messagesToDelete.isEmpty()) { + view?.showActionMode(false) + } + + val filteredData = getFilteredData() + + view?.run { + updateActionModeTitle(messagesToDelete.size) + updateSelectAllMenu(messagesToDelete.containsAll(filteredData)) + } + updateDataInView() + } } fun onUnreadFilterSelected(isChecked: Boolean) { view?.run { onlyUnread = isChecked - onParentViewLoadData(false, onlyUnread, onlyWithAttachments) + loadData(false) } } fun onAttachmentsFilterSelected(isChecked: Boolean) { view?.run { onlyWithAttachments = isChecked - onParentViewLoadData(false, onlyUnread, onlyWithAttachments) + loadData(false) } } - private fun loadData( - forceRefresh: Boolean, - onlyUnread: Boolean, - onlyWithAttachments: Boolean - ) { + private fun loadData(forceRefresh: Boolean) { Timber.i("Loading $folder message data started") flatResourceFlow { @@ -106,54 +210,29 @@ class MessageTabPresenter @Inject constructor( messageRepository.getMessages(student, semester, folder, forceRefresh) } .logResourceStatus("load $folder message") - .onEach { - when (it) { - is Resource.Intermediate -> { - if (it.data.isNotEmpty()) { - view?.run { - enableSwipe(true) - showErrorView(false) - showRefresh(true) - showProgress(false) - showContent(true) - messages = it.data - val filteredData = getFilteredData( - lastSearchQuery, - onlyUnread, - onlyWithAttachments - ) - val messageItems = filteredData.map { message -> - MessageTabDataItem.MessageItem(message) - } - val messageItemsWithHeader = - listOf(MessageTabDataItem.Header) + messageItems + .onResourceData { + messages = it - updateData( - messageItemsWithHeader, - folder.id == MessageFolder.SENT.id - ) - notifyParentDataLoaded() - } - } - } - is Resource.Success -> { - messages = it.data - updateData( - getFilteredData( - lastSearchQuery, - onlyUnread, - onlyWithAttachments - ) - ) - analytics.logEvent( - "load_data", - "type" to "messages", - "items" to it.data.size, - "folder" to folder.name - ) - } - else -> {} + val filteredData = getFilteredData() + + view?.run { + enableSwipe(true) + showErrorView(false) + showProgress(false) + showContent(true) + showEmpty(filteredData.isEmpty()) } + + updateDataInView() + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "messages", + "items" to it.size, + "folder" to folder.name + ) } .onResourceNotLoading { view?.run { @@ -196,56 +275,71 @@ class MessageTabPresenter @Inject constructor( .debounce(250) .map { query -> lastSearchQuery = query - val isOnlyUnread = view?.onlyUnread == true - val isOnlyWithAttachments = view?.onlyWithAttachments == true - getFilteredData(query, isOnlyUnread, isOnlyWithAttachments) + + getFilteredData() } .catch { Timber.e(it) } .collect { Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${it.size}") - updateData(it) + + view?.run { + showEmpty(it.isEmpty()) + showContent(true) + showErrorView(false) + } + + updateDataInView() view?.resetListPosition() } } } - private fun getFilteredData( - query: String, - onlyUnread: Boolean = false, - onlyWithAttachments: Boolean = false - ): List { - if (query.trim().isEmpty()) { + private fun getFilteredData(): List { + if (lastSearchQuery.trim().isEmpty()) { val sortedMessages = messages.sortedByDescending { it.date } return when { - onlyUnread && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments } - onlyUnread -> sortedMessages.filter { it.unread == onlyUnread } + (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments } + (onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread } onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments } else -> sortedMessages } } else { val sortedMessages = messages - .map { it to calculateMatchRatio(it, query) } + .map { it to calculateMatchRatio(it, lastSearchQuery) } .sortedWith(compareBy> { -it.second }.thenByDescending { it.first.date }) .filter { it.second > 6000 } .map { it.first } return when { - onlyUnread && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments } - onlyUnread -> sortedMessages.filter { it.unread == onlyUnread } + (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments } + (onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread } onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments } else -> sortedMessages } } } - private fun updateData(data: List) { - view?.run { - showEmpty(data.isEmpty()) - showContent(true) - showErrorView(false) - val newItems = - listOf(MessageTabDataItem.Header) + data.map { MessageTabDataItem.MessageItem(it) } - updateData(newItems, folder.id == MessageFolder.SENT.id) + private fun updateDataInView() { + val data = getFilteredData() + + val list = buildList { + add( + MessageTabDataItem.FilterHeader( + onlyUnread = onlyUnread.takeIf { folder != MessageFolder.SENT }, + onlyWithAttachments = onlyWithAttachments, + isEnabled = !isActionMode + ) + ) + + addAll(data.map { message -> + MessageTabDataItem.MessageItem( + message = message, + isSelected = messagesToDelete.any { it.id == message.id }, + isActionMode = isActionMode + ) + }) } + + view?.updateData(list) } private fun calculateMatchRatio(message: Message, query: String): Int { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt index a856da3bc..bfa43b209 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt @@ -7,15 +7,15 @@ interface MessageTabView : BaseView { val isViewEmpty: Boolean - var onlyUnread: Boolean? - - var onlyWithAttachments: Boolean - fun initView() fun resetListPosition() - fun updateData(data: List, hide: Boolean) + fun updateData(data: List) + + fun updateActionModeTitle(selectedMessagesSize: Int) + + fun updateSelectAllMenu(isAllSelected: Boolean) fun showProgress(show: Boolean) @@ -25,8 +25,12 @@ interface MessageTabView : BaseView { fun showEmpty(show: Boolean) + fun showMessagesDeleted() + fun showErrorView(show: Boolean) + fun notifyParentShowNewMessage(show: Boolean) + fun setErrorDetails(message: String) fun showRefresh(show: Boolean) @@ -34,4 +38,12 @@ interface MessageTabView : BaseView { fun openMessage(message: Message) fun notifyParentDataLoaded() + + fun notifyParentShowActionMode(show: Boolean) + + fun hideKeyboard() + + fun showActionMode(show: Boolean) + + fun showRecyclerBottomPadding(show: Boolean) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt index 145b12a35..df55abc9c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt @@ -89,6 +89,11 @@ class MoreFragment : BaseFragment(R.layout.fragment_more), if (::presenter.isInitialized) presenter.onViewReselected() } + override fun onFragmentChanged() { + (parentFragmentManager.fragments.find { it is MessageFragment } as MessageFragment?) + ?.onFragmentChanged() + } + override fun updateData(data: List>) { with(moreAdapter) { items = data diff --git a/app/src/main/res/drawable/ic_message_select_all.xml b/app/src/main/res/drawable/ic_message_select_all.xml new file mode 100644 index 000000000..eab195d94 --- /dev/null +++ b/app/src/main/res/drawable/ic_message_select_all.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_message_unselect_all.xml b/app/src/main/res/drawable/ic_message_unselect_all.xml new file mode 100644 index 000000000..c388522e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_message_unselect_all.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/fragment_message_preview.xml b/app/src/main/res/layout/fragment_message_preview.xml index 4b15cf108..5980d820b 100644 --- a/app/src/main/res/layout/fragment_message_preview.xml +++ b/app/src/main/res/layout/fragment_message_preview.xml @@ -9,8 +9,10 @@ android:id="@+id/messagePreviewRecycler" android:layout_width="match_parent" android:layout_height="match_parent" + android:visibility="gone" tools:itemCount="1" - tools:listitem="@layout/item_message_preview" /> + tools:listitem="@layout/item_message_preview" + tools:visibility="visible" /> + + @@ -45,6 +57,7 @@ android:textColor="?android:textColorSecondary" android:textSize="12sp" app:layout_constraintEnd_toStartOf="@id/messageItemAttachmentIcon" + app:layout_constraintStart_toEndOf="@id/messageItemCheckbox" app:layout_constraintStart_toStartOf="@id/messageItemAuthor" app:layout_constraintTop_toBottomOf="@+id/messageItemAuthor" app:layout_goneMarginEnd="0dp" diff --git a/app/src/main/res/menu/action_menu_message_preview.xml b/app/src/main/res/menu/action_menu_message_preview.xml index 4c1332e10..5011e2356 100644 --- a/app/src/main/res/menu/action_menu_message_preview.xml +++ b/app/src/main/res/menu/action_menu_message_preview.xml @@ -19,7 +19,7 @@ android:id="@+id/messagePreviewMenuDelete" android:icon="@drawable/ic_menu_message_delete" android:orderInCategory="1" - android:title="@string/message_delete" + android:title="@string/message_move_to_trash" app:iconTint="@color/material_on_surface_emphasis_medium" app:showAsAction="ifRoom" /> + + + + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index e7c2da332..169beed07 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -287,7 +287,7 @@ Odpověď Poslat dále Odstranit - Přesunout do koše + Přesunout do koše Odstranit natrvalo Zpráva byla úspěšně odstraněna Sdílet diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 68c371d51..a97019446 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -253,7 +253,7 @@ Antwort Weiterleiten Löschen - In den Korb wandern + In den Korb wandern Dauerhaft löschen Nachricht erfolgreich gelöscht Teilen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index da127fe2d..85018247f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -287,7 +287,7 @@ Odpowiedz Prześlij dalej Usuń - Przenieś do kosza + Przenieś do kosza Usuń trwale Wiadomość usunięta pomyślnie Udostępnij diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e6e9834f5..1b3553f1e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -287,7 +287,7 @@ Ответ Переслать Удалить - Перенести в корзину + Перенести в корзину Удалить навсегда Сообщение успешно удалено Поделиться diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index ba701eafd..d33b86654 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -287,7 +287,7 @@ Odpoveď Poslať ďalej Odstrániť - Presunúť do koša + Presunúť do koša Odstrániť natrvalo Správa bola úspešne odstránená Zdieľať diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 7bcffe01b..ec2427c95 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -287,7 +287,7 @@ Відповісти Переслати Видалити - Перемістити у кошик + Перемістити у кошик Видалити назавжди Повідомлення було успішно видалено Поділіться diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee702251e..2763f00de 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -277,11 +277,12 @@ No messages From: To: - Date: %s + Date: %1$s Reply Forward - Delete - Move to trash + Select all + Unselect all + Move to trash Delete permanently Message deleted successfully Share @@ -297,8 +298,8 @@ Read: %s Read by: %1$d of %2$d people - %d message - %d messages + %1$d message + %1$d messages New message @@ -310,6 +311,11 @@ You received %1$d message You received %1$d messages + + %1$d selected + %1$d selected + + Messages deleted From df58aa78aefc610ba14ba083fc87ee297df19b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 31 Mar 2022 09:00:41 +0200 Subject: [PATCH 085/105] New Crowdin updates (#1783) --- app/src/main/res/values-cs/strings.xml | 31 +++++++++++++++++--------- app/src/main/res/values-de/strings.xml | 25 ++++++++++++++------- app/src/main/res/values-pl/strings.xml | 27 +++++++++++++++------- app/src/main/res/values-ru/strings.xml | 29 ++++++++++++++++-------- app/src/main/res/values-sk/strings.xml | 31 +++++++++++++++++--------- app/src/main/res/values-uk/strings.xml | 29 ++++++++++++++++-------- 6 files changed, 118 insertions(+), 54 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 169beed07..555da8dfa 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -55,7 +55,7 @@ Neplatný symbol Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ Vybraný žák je už přihlášen - Symbol najdete na stránce deníku v  Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUjistěte se, že jste na předchozí obrazovce nastavili správnou variantu deníku do pole Variace deníku UONET+. Wulkanowy v tuto chvíli nezjistí předškolní żaków + Symbol najdete na stránce deníku v  Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUjistěte se, že jste na předchozí obrazovce nastavili správnou variantu deníku do pole Variace deníku UONET+ Vyberte žáky, kteří se mají do aplikace přihlásit Jiné možnosti V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí frekvencí, ospravedlnění nepřítomnosti, dokončené lekce, informace o škole a prohlížení seznamu registrovaných zařízení @@ -105,7 +105,9 @@ Semestr Body Vysvětlivky - Průměr: %1$s + Průměr třídy: %1$s + Váš průměr: %1$s + Vaše známka: %1$s Třída Žák @@ -283,10 +285,11 @@ Žádné zprávy Od: Komu: - Datum: %s + Datum: %1$s Odpověď Poslat dále - Odstranit + Vybrat vše + Odznačit vše Přesunout do koše Odstranit natrvalo Zpráva byla úspěšně odstraněna @@ -303,10 +306,10 @@ Přečtena: %s Přečtena přes: %1$d z %2$d osob - %d zpráva - %d zprávy - %d zpráv - %d zpráv + %1$d zpráva + %1$d zprávy + %1$d zpráv + %1$d zpráv Nová zpráva @@ -322,6 +325,13 @@ Máte %1$d nových zpráv Máte %1$d nových zpráv + + %1$d vybraná + %1$d vybrané + %1$d vybraných + %1$d vybraných + + Zprávy odstraněné Žádné informace o poznámkách Body @@ -383,8 +393,8 @@ Žádné informace o domácích úkolech - Označit jako hotové - Neudělané + Vykonané + Nevykonané Přidat domácí úkol Domácí úkol byl úspěšně přidán Domácí úkol byl úspěšně odstraněn @@ -647,6 +657,7 @@ Zkopírováno Vrátit Změnit + Přidat do kalendáře Žádné lekce Vybrat motiv diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a97019446..8492f6460 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -55,7 +55,7 @@ Ungültige symbol 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 + 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 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 @@ -105,7 +105,9 @@ Semester Punkte Legende - Durchschnitt: %1$s + Klassendurchschnitt: %1$s + Dein Durchschnitt: %1$s + Deine Note: %1$s Klasse Schüler @@ -249,11 +251,12 @@ Keine Nachrichten Von: An: - Datum: %s + Datum: %1$s Antwort Weiterleiten - Löschen - In den Korb wandern + Alle auswählen + Alle abwählen + In Papierkorb verschieben Dauerhaft löschen Nachricht erfolgreich gelöscht Teilen @@ -269,8 +272,8 @@ Lesen: %s Lesen von: %1$d von %2$d Personen - %d nachricht - %d nachrichten + %1$d Nachricht + %1$d Nachrichten Neu nachricht @@ -282,6 +285,11 @@ Du hast %1$d nachricht bekommen Du hast %1$d nachrichten bekommen + + %1$d ausgewählt + %1$d ausgewählt + + Nachrichten gelöscht Keine Informationen über Eintragen Punkte @@ -561,6 +569,7 @@ Kopiert lösen Ändern + Zum Kalender hinzufügen Keine Lektionen Thema wählen @@ -597,7 +606,7 @@ Offizielle App-Benachrichtigungen erfassen 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 + Mit dieser Funktion können Sie einen Ersatz für Push-Benachrichtigungen erhalten, wie in der offiziellen App. Alles, was Sie tun müssen, ist es Wulkanowy erlauben, alle Benachrichtigungen in Ihren Systemeinstellungen zu erhalten.\n\nWie funktioniert es?\nWenn Sie eine Benachrichtigung in Dziennik VULCAN erhalten, Wulkanowy wird benachrichtigt (dafür sind diese zusätzlichen Berechtigungen) und wird eine Synchronisierung auslösen, so dass eine eigene Benachrichtigung gesendet werden kann.\n\nNUR FÜR FORTGESCHRITTENE BENUTZER 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 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 85018247f..2c8a83cca 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -55,7 +55,7 @@ Nieprawidłowy symbol Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+ Wybrany uczeń jest już zalogowany - Symbol znajdziesz na stronie dziennika w Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUpewnij się, że w polu Dziennik UONET+ na poprzednim ekranie została ustawiona odpowiednia odmiana dziennika.\n\nWulkanowy na chwilę obecną nie wykrywa uczniów przedszkolnych (z zerówki) + Symbol znajdziesz na stronie dziennika w Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUpewnij się, że w polu Dziennik UONET+ na poprzednim ekranie została ustawiona odpowiednia odmiana dziennika Wybierz uczniów do zalogowania w aplikacji Inne opcje W tym trybie nie działa szczęśliwy numerek, uczeń na tle klasy, podsumowanie frekwencji, usprawiedliwianie nieobecności, lekcje zrealizowane, informacje o szkole i podgląd listy zarejestrowanych urządzeń @@ -105,7 +105,9 @@ Semestralne Punkty Legenda - Średnia: %1$s + Średnia klasy: %1$s + Twoja średnia: %1$s + Twoja ocena: %1$s Klasa Uczeń @@ -283,10 +285,11 @@ Brak wiadomości Od: Do: - Data: %s + Data: %1$s Odpowiedz Prześlij dalej - Usuń + Zaznacz wszystkie + Odznacz wszystkie Przenieś do kosza Usuń trwale Wiadomość usunięta pomyślnie @@ -303,10 +306,10 @@ Przeczytana: %s Przeczytana przez: %1$d z %2$d osób - %d wiadomość - %d wiadomości - %d wiadomości - %d wiadomości + %1$d wiadomość + %1$d wiadomości + %1$d wiadomości + %1$d wiadomości Nowa wiadomość @@ -322,6 +325,13 @@ Masz %1$d nowych wiadomości Masz %1$d nowych wiadomości + + %1$d wybrana + %1$d wybrane + %1$d wybranych + %1$d wybranych + + Wiadomości zostały usunięte Brak informacji o uwagach Punkty @@ -647,6 +657,7 @@ Skopiowano Cofnij Zmień + Dodaj do kalendarza Brak lekcji Wybierz motyw diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1b3553f1e..1ddcaf4c5 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -105,7 +105,9 @@ За семестр Баллы Легенда - Средняя: %1$s + Средняя класса: %1$s + Ваша средний: %1$s + Ваша оценка: %1$s Класс Студент @@ -283,10 +285,11 @@ Нет сообщений От: Кому: - Дата: %s + Дата: %1$s Ответ Переслать - Удалить + Выбрать всё + Снять выбор Перенести в корзину Удалить навсегда Сообщение успешно удалено @@ -303,10 +306,10 @@ Чтение: %s Прочитано: %1$d из %2$d человек - %d сообщение - %d сообщения - %d сообщений - %d сообщений + %1$d сообщение + %1$d сообщений + %1$d сообщений + %1$d сообщений Новое сообщение @@ -322,6 +325,13 @@ Вы получили %1$d новых сообщений Вы получили %1$d новых сообщений + + %1$d выбрано + %1$d выбрано + %1$d выбрано + %1$d выбрано + + Сообщение удалено Нет информации о заметках Баллы @@ -383,8 +393,8 @@ Нет домашних заданий - Отметить как выполненное - Отметить как невыполненное + Завершено + Не завершено Добавить домашнюю работу Домашняя работа успешно добавлена Домашняя работа успешно удалена @@ -647,6 +657,7 @@ Скопировано Отменить Изменить + Добавить в календарь Нет уроков Выбрать тему diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index d33b86654..804473add 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -55,7 +55,7 @@ Neplatný symbol Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+ Vybraný žiak už je prihlásený - Symbol nájdete na stránke denníka v  Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUistite sa, že ste na predchádzajúcu obrazovke nastaviť správny variant denníka do poľa Variácie denníka UONET+. Wulkanowy v túto chvíľu nezistí predškolské żaków + Symbol nájdete na stránke denníka v  Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUistite sa, že ste na predchádzajúcu obrazovke nastaviť správny variant denníka do poľa Variácie denníka UONET+ Vyberte žiakov, ktorí sa majú do aplikácie prihlásiť Iné možnosti V tomto režime nefungujú nasledovné: šťastné číslo, štatistiky triedy, zhrnutie frekvencií, ospravedlnenie neprítomnosti, dokončené lekcie, informácie o škole a prezeranie zoznamu registrovaných zariadení @@ -105,7 +105,9 @@ Semester Body Vysvetlivky - Priemer: %1$s + Priemer triedy: %1$s + Váš priemer: %1$s + Vaša známka: %1$s Trieda Žiák @@ -283,10 +285,11 @@ Žiadne správy Od: Komu: - Dátum: %s + Dátum: %1$s Odpoveď Poslať ďalej - Odstrániť + Vybrať všetko + Odznačiť všetko Presunúť do koša Odstrániť natrvalo Správa bola úspešne odstránená @@ -303,10 +306,10 @@ Prečítaná: %s Prečítaná cez: %1$d z %2$d osôb - %d správa - %d správy - %d správ - %d správ + %1$d správa + %1$d správy + %1$d správ + %1$d správ Nová správa @@ -322,6 +325,13 @@ Máte %1$d nových správ Máte %1$d nových správ + + %1$d vybraná + %1$d vybrané + %1$d vybraných + %1$d vybraných + + Správy odstránené Žiadne informácie o poznámkach Body @@ -383,8 +393,8 @@ Žiadne informácie o domácich úlohách - Označiť ako hotové - Nevyrobené + Vykonané + Nevykonané Pridať domácu úlohu Domáca úloha bola úspešně pridaná Domáca úloha bola úspešně odstránená @@ -647,6 +657,7 @@ Skopírované Vrátiť Zmeniť + Pridať do kalendára Žiadne lekcie Vybrať motív diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index ec2427c95..c53161e71 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -55,7 +55,7 @@ Неправильний симбвол Студента не знайдено Перевірте символ та обраний варіант реєстру UONET+ Даного учня вже авторизовано - Символ можна знайти на сторінці реєстру в   Учень →   Мобільний доступ →   Додайте мобільне приладдя .\n\nПереконайтесь, що ви встановили відповідний варіант реєстру в полі UONET + варіант реєстрації на попередньому екрані. На даний момент Wulkanowy не виявляє учнів дошкільних закладів + Символ можна знайти на сторінці реєстрації в   Учень →   Мобільний доступ →   Додайте мобільне приладдя .\n\nПереконайтесь, що ви встановили відповідний варіант реєстру в полі UONET + варіант реєстрації на попередньому екрані. На даний момент Wulkanowy не виявляє учнів дошкільних закладів Виберіть учнів для авторизації в додатку Інші варіанти У цьому режимі не працюють: щасливий номер, статистика класу по оцінкам, статистика відвідуваності і уроків, інформація про школу і список зареєстрованних пристроїв @@ -105,7 +105,9 @@ Семестрові Бали Умовні позначення - Середня оцінка: %1$s + Середня классу: %1$s + Ваша середня: %1$s + Ваша оцінка: %1$s Клас Учень @@ -283,11 +285,12 @@ Нема повідомлень Від: Кому: - Дата: %s + Дата: %1$s Відповісти Переслати - Видалити - Перемістити у кошик + Вибрати все + Відмінити вибір + Перемістити до кошика Видалити назавжди Повідомлення було успішно видалено Поділіться @@ -303,10 +306,10 @@ Читання: %s Прочитанно:%1$d через %2$d людей - %d повідомлення - %d повідомлення - %d повідомлень - %d повідомлень + %1$d повідомлення + %1$d повідомлень + %1$d повідомлень + %1$d повідомлень Нове повідомлення @@ -322,6 +325,13 @@ Ви отримали %1$d нових повідомлень Ви отримали %1$d нових повідомлень + + %1$d вибрано + вибрано %1$d + вибрано %1$d + %1$d вибрано + + Повідомлення видалені Брак інформації о зауваженнях Бали @@ -647,6 +657,7 @@ Скопійовано Відмінити Змінити + Додати у календар Брак уроків Увібрати тему From 884d443c5ba180f1f5cbd6f27d97434a2ad75b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 2 Apr 2022 22:01:34 +0200 Subject: [PATCH 086/105] Version 1.6.0 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c7e42fde8..9c056c542 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,8 +22,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 31 - versionCode 103 - versionName "1.5.0" + versionCode 104 + versionName "1.6.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -178,7 +178,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:fba873461e" + implementation "io.github.wulkanowy:sdk:1.6.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 6e768ac3a..f66c25495 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,8 +1,9 @@ -Wersja 1.5.0 +Wersja 1.6.0 -- 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 +- dodaliśmy możliwość usuwania wielu wiadomości jednocześnie +- dodaliśmy opcję szybkiego dodawania sprawdzianów do kalendarza +- dodaliśmy średnią ucznia w wykresach ocen klasy +- naprawiliśmy rzadki błąd dotyczący problemów z automatycznym odświeżaniem ekranu startowego +- naprawiliśmy błąd z liczeniem średniej w drugim semestrze Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 84067126a1d711f0232adad5ada3956df4c2c884 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 08:09:46 +0000 Subject: [PATCH 087/105] Bump hianalytics from 6.4.1.300 to 6.4.1.301 (#1819) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 9c056c542..92e160106 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:20.6.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.300' + hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.200' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 3347e8fba8c42bb5d40cd496bbd4b2891b97235f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 08:10:07 +0000 Subject: [PATCH 088/105] Bump kotlin_version from 1.6.10 to 1.6.20 (#1818) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 742d39569..9aa30944f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.6.10' + kotlin_version = '1.6.20' about_libraries = '8.9.4' hilt_version = "2.41" } From 6531061b48449693965fea7d8bc64fea90592dca Mon Sep 17 00:00:00 2001 From: Mat Lee <98318975+zlewchan@users.noreply.github.com> Date: Tue, 5 Apr 2022 10:30:49 +0200 Subject: [PATCH 089/105] Fix text aligment in exam dialog (#1821) --- app/src/main/res/layout/dialog_exam.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/dialog_exam.xml b/app/src/main/res/layout/dialog_exam.xml index 57ed9d45b..0d04b1fac 100644 --- a/app/src/main/res/layout/dialog_exam.xml +++ b/app/src/main/res/layout/dialog_exam.xml @@ -146,7 +146,7 @@ android:id="@+id/examDialogDeadlineDateValue" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="0dp" + android:layout_marginStart="16dp" android:layout_marginEnd="24dp" android:paddingStart="0dp" android:paddingEnd="16dp" @@ -162,7 +162,7 @@ android:id="@+id/examDialogEntryDateTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="0dp" + android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:layout_marginEnd="24dp" android:text="@string/exam_entry_date" From d473d53879385891aefcb4d9a560ef3e34c6e5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 5 Apr 2022 13:56:11 +0200 Subject: [PATCH 090/105] Add standard register variant as translatable string (#1824) --- app/src/main/res/values/api_hosts.xml | 2 +- app/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index b3b434e14..8413d68e4 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -1,7 +1,7 @@ - Standardowa + @string/login_host_standard Opolska eSzkoła Gdańska Platforma Edukacyjna Lubelski Portal Oświatowy diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2763f00de..ff25da7f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,6 +77,7 @@ Recover your account Recover Student is already signed in + Standard From 679cf2554de22d824cd39fbc6ac3db94bbb96686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 6 Apr 2022 08:01:11 +0200 Subject: [PATCH 091/105] Fix text in lucky number widget (#1825) --- .../ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index e016c07e6..e03e3e90e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -78,7 +78,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { .apply { setTextViewText( R.id.luckyNumberWidgetNumber, - luckyNumber.dataOrNull?.toString() ?: "#" + luckyNumber.dataOrNull?.luckyNumber?.toString() ?: "#" ) setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) } From cb4ae21903680b4fc0c78496f81fe3727748ee4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 6 Apr 2022 08:01:41 +0200 Subject: [PATCH 092/105] Replace Serializable to Parcelable in Destination (#1823) --- .../wulkanowy/ui/modules/Destination.kt | 19 +++++++++++++++++-- .../wulkanowy/ui/modules/main/MainActivity.kt | 2 +- .../ui/modules/splash/SplashActivity.kt | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt index f49c48891..f8c456fe6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules +import android.os.Parcelable import androidx.fragment.app.Fragment import io.github.wulkanowy.data.serializers.LocalDateSerializer import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment @@ -15,11 +16,12 @@ import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment +import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable import java.time.LocalDate @Serializable -sealed class Destination private constructor() : java.io.Serializable { +sealed class Destination private constructor() : Parcelable { /* Type in children classes have to be as getter to avoid null in enums @@ -45,30 +47,35 @@ sealed class Destination private constructor() : java.io.Serializable { MESSAGE(Message); } + @Parcelize @Serializable object Dashboard : Destination() { override val type get() = Type.DASHBOARD override val fragment get() = DashboardFragment.newInstance() } + @Parcelize @Serializable object Grade : Destination() { override val type get() = Type.GRADE override val fragment get() = GradeFragment.newInstance() } + @Parcelize @Serializable object Attendance : Destination() { override val type get() = Type.ATTENDANCE override val fragment get() = AttendanceFragment.newInstance() } + @Parcelize @Serializable object Exam : Destination() { override val type get() = Type.EXAM override val fragment get() = ExamFragment.newInstance() } + @Parcelize @Serializable data class Timetable( @Serializable(with = LocalDateSerializer::class) @@ -78,51 +85,59 @@ sealed class Destination private constructor() : java.io.Serializable { override val fragment get() = TimetableFragment.newInstance(date) } + @Parcelize @Serializable object Homework : Destination() { override val type get() = Type.HOMEWORK override val fragment get() = HomeworkFragment.newInstance() } + @Parcelize @Serializable object Note : Destination() { override val type get() = Type.NOTE override val fragment get() = NoteFragment.newInstance() } + @Parcelize @Serializable object Conference : Destination() { override val type get() = Type.CONFERENCE override val fragment get() = ConferenceFragment.newInstance() } + @Parcelize @Serializable object SchoolAnnouncement : Destination() { override val type get() = Type.SCHOOL_ANNOUNCEMENT override val fragment get() = SchoolAnnouncementFragment.newInstance() } + @Parcelize @Serializable object School : Destination() { override val type get() = Type.SCHOOL override val fragment get() = SchoolFragment.newInstance() } + @Parcelize @Serializable object LuckyNumber : Destination() { override val type get() = Type.LUCKY_NUMBER override val fragment get() = LuckyNumberFragment.newInstance() } + @Parcelize @Serializable object More : Destination() { override val type get() = Type.MORE override val fragment get() = MoreFragment.newInstance() } + @Parcelize @Serializable object Message : Destination() { override val type get() = Type.MESSAGE override val fragment get() = MessageFragment.newInstance() } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 0cd38ac7d..d1f324475 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -86,7 +86,7 @@ class MainActivity : BaseActivity(), MainVie messageContainer = binding.mainMessageContainer updateHelper.messageContainer = binding.mainFragmentContainer - val destination = (intent.getSerializableExtra(EXTRA_START_DESTINATION) as Destination?) + val destination = (intent.getParcelableExtra(EXTRA_START_DESTINATION) as Destination?) ?.takeIf { savedInstanceState == null } presenter.onAttachView(this, destination) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt index a86024e49..24347e735 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt @@ -45,7 +45,7 @@ class SplashActivity : BaseActivity(), SplashView installSplashScreen().setKeepOnScreenCondition { true } val externalLink = intent?.getStringExtra(EXTRA_EXTERNAL_URL) - val startDestination = intent?.getSerializableExtra(EXTRA_START_DESTINATION) as Destination? + val startDestination = intent?.getParcelableExtra(EXTRA_START_DESTINATION) as Destination? ?: shortcutsHelper.getDestination(intent) presenter.onAttachView(this, externalLink, startDestination) From 0a87df3d827f7f955366c45c1bccc1e13226fe1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 6 Apr 2022 17:19:38 +0200 Subject: [PATCH 093/105] New Crowdin updates (#1817) --- app/src/main/res/values-cs/strings.xml | 7 ++++--- app/src/main/res/values-de/strings.xml | 1 + 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 + 6 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 555da8dfa..11054a0e5 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -71,6 +71,7 @@ Obnovte svůj účet Obnovit Žák je už přihlášen + Standardní Manažer účtů Přihlásit se @@ -93,9 +94,9 @@ Předpokládaná známka Vypočítaný průměr Jak funguje vypočítaný průměr? - Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\nPrůměr známek pouze z vybraného semestru:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\nPrůměr průměrů z obou semestrů:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru součtených průměrů\n\nPrůměr známek z celého roku:\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru součtených průměrů + Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\nPrůměr známek pouze z vybraného semestru:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\nPrůměr průměrů z obou semestrů:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru sečtených průměrů\n\nPrůměr známek z celého roku:\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru součtených průměrů Jak funguje konečný průměr? - Konečný průměr je aritmetický průměr vypočítaný ze všech aktuálně dostupných konečných známek v daném semestru.\n\nSchéma výpočtu se skládá z následujících kroků:\n1. Sčítání konečných známek zadaných učiteli\n2. Děleno počtem předmětů, pro které už byly vydány známky + Konečný průměr je aritmetický průměr vypočítaný ze všech aktuálně dostupných konečných známek v daném semestru.\n\nSchéma výpočtu se skládá z následujících kroků:\n1. Sčítání konečných známek zadaných učiteli\n2. Děleno počtem předmětů, pro které už byly uděleny známky Konečný průměr z %1$d z %2$d předmětů Shrnutí @@ -219,7 +220,7 @@ Čas ukončení musí být pozdější než čas zahájení Shrnutí frekvencí - Neprítomnosť zo školských dôvodov + Nepřítomnost ze školních důvodů Omluvená nepřítomnost Neomluvená nepřítomnost Osvobození diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8492f6460..86308aa19 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -71,6 +71,7 @@ Ihr Konto wiederherstellen Wiederherstellen Student ist bereits angemeldet + Standard Kundenbetreuer Anmelden diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2c8a83cca..1607b17c6 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -71,6 +71,7 @@ Przywróć swoje konto Przywróć Uczeń jest już zalogowany + Standardowa Menadżer kont Zaloguj się diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1ddcaf4c5..909a627cb 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -71,6 +71,7 @@ Восстановите свой аккаунт Восстановить Студент уже вошел в систему + Стандартный Менеджер аккаунтов Войти diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 804473add..5ebd1e76b 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -71,6 +71,7 @@ Obnovte svoj účet Obnoviť Žiak je už prihlásený + Štandardná Manažér účtov Prihlásiť sa diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index c53161e71..7e01f70b6 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -71,6 +71,7 @@ Відновіть свій обліковий запис Відновити Учень вже увійшов до системи + Стандартний Менеджер аккаунтів Увійти From 391ee6e621e4a4d125fcf0acf0a93e79ed031e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 6 Apr 2022 17:27:27 +0200 Subject: [PATCH 094/105] Version 1.6.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 92e160106..56137fffa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,8 +22,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 31 - versionCode 104 - versionName "1.6.0" + versionCode 105 + versionName "1.6.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" From 1bc59cfa7f800ecadbe78cc305c9ab21cad09634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 10 Apr 2022 20:23:46 +0200 Subject: [PATCH 095/105] Another attempt to fix destination crash (#1826) --- .../wulkanowy/ui/modules/Destination.kt | 58 +++++++++---------- .../wulkanowy/ui/modules/main/MainActivity.kt | 4 +- .../ui/modules/main/MainPresenter.kt | 6 +- .../NotificationsCenterFragment.kt | 2 +- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt index f8c456fe6..73a680cfe 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt @@ -21,15 +21,15 @@ import kotlinx.serialization.Serializable import java.time.LocalDate @Serializable -sealed class Destination private constructor() : Parcelable { +sealed class Destination : Parcelable { /* Type in children classes have to be as getter to avoid null in enums https://stackoverflow.com/questions/68866453/kotlin-enum-val-is-returning-null-despite-being-set-at-compile-time */ - abstract val type: Type + abstract val destinationType: Type - abstract val fragment: Fragment + abstract val destinationFragment: Fragment enum class Type(val defaultDestination: Destination) { DASHBOARD(Dashboard), @@ -50,29 +50,29 @@ sealed class Destination private constructor() : Parcelable { @Parcelize @Serializable object Dashboard : Destination() { - override val type get() = Type.DASHBOARD - override val fragment get() = DashboardFragment.newInstance() + override val destinationType get() = Type.DASHBOARD + override val destinationFragment get() = DashboardFragment.newInstance() } @Parcelize @Serializable object Grade : Destination() { - override val type get() = Type.GRADE - override val fragment get() = GradeFragment.newInstance() + override val destinationType get() = Type.GRADE + override val destinationFragment get() = GradeFragment.newInstance() } @Parcelize @Serializable object Attendance : Destination() { - override val type get() = Type.ATTENDANCE - override val fragment get() = AttendanceFragment.newInstance() + override val destinationType get() = Type.ATTENDANCE + override val destinationFragment get() = AttendanceFragment.newInstance() } @Parcelize @Serializable object Exam : Destination() { - override val type get() = Type.EXAM - override val fragment get() = ExamFragment.newInstance() + override val destinationType get() = Type.EXAM + override val destinationFragment get() = ExamFragment.newInstance() } @Parcelize @@ -81,63 +81,63 @@ sealed class Destination private constructor() : Parcelable { @Serializable(with = LocalDateSerializer::class) private val date: LocalDate? = null ) : Destination() { - override val type get() = Type.TIMETABLE - override val fragment get() = TimetableFragment.newInstance(date) + override val destinationType get() = Type.TIMETABLE + override val destinationFragment get() = TimetableFragment.newInstance(date) } @Parcelize @Serializable object Homework : Destination() { - override val type get() = Type.HOMEWORK - override val fragment get() = HomeworkFragment.newInstance() + override val destinationType get() = Type.HOMEWORK + override val destinationFragment get() = HomeworkFragment.newInstance() } @Parcelize @Serializable object Note : Destination() { - override val type get() = Type.NOTE - override val fragment get() = NoteFragment.newInstance() + override val destinationType get() = Type.NOTE + override val destinationFragment get() = NoteFragment.newInstance() } @Parcelize @Serializable object Conference : Destination() { - override val type get() = Type.CONFERENCE - override val fragment get() = ConferenceFragment.newInstance() + override val destinationType get() = Type.CONFERENCE + override val destinationFragment get() = ConferenceFragment.newInstance() } @Parcelize @Serializable object SchoolAnnouncement : Destination() { - override val type get() = Type.SCHOOL_ANNOUNCEMENT - override val fragment get() = SchoolAnnouncementFragment.newInstance() + override val destinationType get() = Type.SCHOOL_ANNOUNCEMENT + override val destinationFragment get() = SchoolAnnouncementFragment.newInstance() } @Parcelize @Serializable object School : Destination() { - override val type get() = Type.SCHOOL - override val fragment get() = SchoolFragment.newInstance() + override val destinationType get() = Type.SCHOOL + override val destinationFragment get() = SchoolFragment.newInstance() } @Parcelize @Serializable object LuckyNumber : Destination() { - override val type get() = Type.LUCKY_NUMBER - override val fragment get() = LuckyNumberFragment.newInstance() + override val destinationType get() = Type.LUCKY_NUMBER + override val destinationFragment get() = LuckyNumberFragment.newInstance() } @Parcelize @Serializable object More : Destination() { - override val type get() = Type.MORE - override val fragment get() = MoreFragment.newInstance() + override val destinationType get() = Type.MORE + override val destinationFragment get() = MoreFragment.newInstance() } @Parcelize @Serializable object Message : Destination() { - override val type get() = Type.MESSAGE - override val fragment get() = MessageFragment.newInstance() + override val destinationType get() = Type.MESSAGE + override val destinationFragment get() = MessageFragment.newInstance() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index d1f324475..b6d41e2c1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -129,7 +129,7 @@ class MainActivity : BaseActivity(), MainVie ) } fragmentHideStrategy = HIDE - rootFragments = rootDestinations.map { it.fragment } + rootFragments = rootDestinations.map { it.destinationFragment } initialize(startMenuIndex, savedInstanceState) } @@ -230,7 +230,7 @@ class MainActivity : BaseActivity(), MainVie } override fun openMoreDestination(destination: Destination) { - pushView(destination.fragment) + pushView(destination.destinationFragment) } override fun notifyMenuViewReselected() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index e01497b9a..cb414fcbc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -45,8 +45,8 @@ class MainPresenter @Inject constructor( private val Destination?.startMenuIndex get() = when { this == null -> prefRepository.startMenuIndex - type in rootDestinationTypeList -> { - rootDestinationTypeList.indexOf(type) + destinationType in rootDestinationTypeList -> { + rootDestinationTypeList.indexOf(destinationType) } else -> 4 } @@ -56,7 +56,7 @@ class MainPresenter @Inject constructor( val startMenuIndex = initDestination.startMenuIndex val destinations = rootDestinationTypeList.map { - if (it == initDestination?.type) initDestination else it.defaultDestination + if (it == initDestination?.destinationType) initDestination else it.defaultDestination } view.initView(startMenuIndex, destinations) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt index 4f1943f48..ca71910a4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt @@ -43,7 +43,7 @@ class NotificationsCenterFragment : override fun initView() { notificationsCenterAdapter.onItemClickListener = { notification -> - (requireActivity() as MainActivity).pushView(notification.destination.fragment) + (requireActivity() as MainActivity).pushView(notification.destination.destinationFragment) } with(binding.notificationsCenterRecycler) { From e6f56a74a4360163aa5bba98bea7bc9b5b621b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 10 Apr 2022 20:37:10 +0200 Subject: [PATCH 096/105] Version 1.6.2 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 56137fffa..2f77712fa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,8 +22,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 31 - versionCode 105 - versionName "1.6.1" + versionCode 106 + versionName "1.6.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -153,7 +153,7 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS - userFraction = 0.25d + userFraction = 0.50d updatePriority = 1 enabled.set(false) } 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 f66c25495..4a3b1e2f6 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,4 +1,4 @@ -Wersja 1.6.0 +Wersja 1.6.2 - dodaliśmy możliwość usuwania wielu wiadomości jednocześnie - dodaliśmy opcję szybkiego dodawania sprawdzianów do kalendarza From b0885510056c8d111565b8a9fa84888e44d7c396 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 02:04:53 +0000 Subject: [PATCH 097/105] Bump hianalytics from 6.4.1.301 to 6.4.1.302 (#1832) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2f77712fa..ef6749a2a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:20.6.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.301' + hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.302' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.200' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 88576271e235444246e7a98289b2a6a2ab9248dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 02:05:12 +0000 Subject: [PATCH 098/105] Bump agcp from 1.6.5.200 to 1.6.5.300 (#1831) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9aa30944f..a306227d5 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.1.2' 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.5.200' + classpath 'com.huawei.agconnect:agcp:1.6.5.300' 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.3" From 3caabd3e0e3a007b56d195cb49819b4b9bc58ebd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 02:06:04 +0000 Subject: [PATCH 099/105] Bump coroutines from 1.6.0 to 1.6.1 (#1828) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ef6749a2a..3ec1938d8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -174,7 +174,7 @@ ext { room = "2.4.2" chucker = "3.5.2" mockk = "1.12.2" - coroutines = "1.6.0" + coroutines = "1.6.1" } dependencies { From f912aac140442ff591ccf3fde174c6a8ce102323 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 02:12:20 +0000 Subject: [PATCH 100/105] Bump gradle from 7.1.2 to 7.1.3 (#1829) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a306227d5..80b2e4b74 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:7.1.3' 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.5.300' From a73f39e59cbdfc0c8926e335787e16a9cffd8edc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 02:13:09 +0000 Subject: [PATCH 101/105] Bump agconnect-crash from 1.6.5.200 to 1.6.5.300 (#1830) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3ec1938d8..30c81b55b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:20.6.0' hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.302' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.200' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 447ece3696efc0927bbdcd4c57f5282bd68bad15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 16 Apr 2022 12:17:22 +0200 Subject: [PATCH 102/105] Replace destination parcelable with destination json string (#1833) --- .../services/shortcuts/ShortcutsHelper.kt | 58 ++++--------------- .../wulkanowy/ui/modules/Destination.kt | 17 +----- .../wulkanowy/ui/modules/main/MainActivity.kt | 13 +++-- .../ui/modules/main/MainPresenter.kt | 7 ++- .../ui/modules/splash/SplashActivity.kt | 12 ++-- .../ui/modules/splash/SplashPresenter.kt | 7 ++- .../ui/modules/main/MainPresenterTest.kt | 18 +++--- .../ui/modules/splash/SplashPresenterTest.kt | 3 +- 8 files changed, 49 insertions(+), 86 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt index ee31af46b..5e59aa54b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt @@ -15,79 +15,41 @@ import javax.inject.Singleton @Singleton class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) { - // Destination cannot be used here as shortcuts - // require their intents to only use primitive types (see PersistableBundle.isValidType). - - private val destinations = mapOf( - "grade" to Destination.Grade, - "attendance" to Destination.Attendance, - "exam" to Destination.Exam, - "timetable" to Destination.Timetable() - ) - - init { - initializeShortcuts() - } - - fun getDestination(intent: Intent) = - destinations[intent.getStringExtra(EXTRA_SHORTCUT_DESTINATION_ID)] - - private fun initializeShortcuts() { + fun initializeShortcuts() { val shortcutsInfo = listOf( ShortcutInfoCompat.Builder(context, "grade_shortcut") .setShortLabel(context.getString(R.string.grade_title)) .setLongLabel(context.getString(R.string.grade_title)) .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade)) - .setIntent(SplashActivity.getStartIntent(context) - .apply { - action = Intent.ACTION_VIEW - putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "grade") - } - ) + .setIntent(SplashActivity.getStartIntent(context, Destination.Grade) + .apply { action = Intent.ACTION_VIEW }) .build(), ShortcutInfoCompat.Builder(context, "attendance_shortcut") .setShortLabel(context.getString(R.string.attendance_title)) .setLongLabel(context.getString(R.string.attendance_title)) .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance)) - .setIntent(SplashActivity.getStartIntent(context) - .apply { - action = Intent.ACTION_VIEW - putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "attendance") - } - ) + .setIntent(SplashActivity.getStartIntent(context, Destination.Attendance) + .apply { action = Intent.ACTION_VIEW }) .build(), ShortcutInfoCompat.Builder(context, "exam_shortcut") .setShortLabel(context.getString(R.string.exam_title)) .setLongLabel(context.getString(R.string.exam_title)) .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam)) - .setIntent(SplashActivity.getStartIntent(context) - .apply { - action = Intent.ACTION_VIEW - putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "exam") - } - ) + .setIntent(SplashActivity.getStartIntent(context, Destination.Exam) + .apply { action = Intent.ACTION_VIEW }) .build(), ShortcutInfoCompat.Builder(context, "timetable_shortcut") .setShortLabel(context.getString(R.string.timetable_title)) .setLongLabel(context.getString(R.string.timetable_title)) .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable)) - .setIntent(SplashActivity.getStartIntent(context) - .apply { - action = Intent.ACTION_VIEW - putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "timetable") - } - ) + .setIntent(SplashActivity.getStartIntent(context, Destination.Timetable()) + .apply { action = Intent.ACTION_VIEW }) .build() ) shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) } } - - private companion object { - - private const val EXTRA_SHORTCUT_DESTINATION_ID = "shortcut_destination_id" - } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt index 73a680cfe..561419a05 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules -import android.os.Parcelable import androidx.fragment.app.Fragment import io.github.wulkanowy.data.serializers.LocalDateSerializer import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment @@ -16,12 +15,11 @@ import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment -import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable import java.time.LocalDate @Serializable -sealed class Destination : Parcelable { +sealed class Destination { /* Type in children classes have to be as getter to avoid null in enums @@ -47,35 +45,30 @@ sealed class Destination : Parcelable { MESSAGE(Message); } - @Parcelize @Serializable object Dashboard : Destination() { override val destinationType get() = Type.DASHBOARD override val destinationFragment get() = DashboardFragment.newInstance() } - @Parcelize @Serializable object Grade : Destination() { override val destinationType get() = Type.GRADE override val destinationFragment get() = GradeFragment.newInstance() } - @Parcelize @Serializable object Attendance : Destination() { override val destinationType get() = Type.ATTENDANCE override val destinationFragment get() = AttendanceFragment.newInstance() } - @Parcelize @Serializable object Exam : Destination() { override val destinationType get() = Type.EXAM override val destinationFragment get() = ExamFragment.newInstance() } - @Parcelize @Serializable data class Timetable( @Serializable(with = LocalDateSerializer::class) @@ -85,56 +78,48 @@ sealed class Destination : Parcelable { override val destinationFragment get() = TimetableFragment.newInstance(date) } - @Parcelize @Serializable object Homework : Destination() { override val destinationType get() = Type.HOMEWORK override val destinationFragment get() = HomeworkFragment.newInstance() } - @Parcelize @Serializable object Note : Destination() { override val destinationType get() = Type.NOTE override val destinationFragment get() = NoteFragment.newInstance() } - @Parcelize @Serializable object Conference : Destination() { override val destinationType get() = Type.CONFERENCE override val destinationFragment get() = ConferenceFragment.newInstance() } - @Parcelize @Serializable object SchoolAnnouncement : Destination() { override val destinationType get() = Type.SCHOOL_ANNOUNCEMENT override val destinationFragment get() = SchoolAnnouncementFragment.newInstance() } - @Parcelize @Serializable object School : Destination() { override val destinationType get() = Type.SCHOOL override val destinationFragment get() = SchoolFragment.newInstance() } - @Parcelize @Serializable object LuckyNumber : Destination() { override val destinationType get() = Type.LUCKY_NUMBER override val destinationFragment get() = LuckyNumberFragment.newInstance() } - @Parcelize @Serializable object More : Destination() { override val destinationType get() = Type.MORE override val destinationFragment get() = MoreFragment.newInstance() } - @Parcelize @Serializable object Message : Destination() { override val destinationType get() = Type.MESSAGE diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index b6d41e2c1..1bfc8ba58 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -24,6 +24,8 @@ import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog import io.github.wulkanowy.utils.* +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import timber.log.Timber import javax.inject.Inject @@ -55,13 +57,13 @@ class MainActivity : BaseActivity(), MainVie companion object { - private const val EXTRA_START_DESTINATION = "start_destination" + private const val EXTRA_START_DESTINATION = "start_destination_json" fun getStartIntent( context: Context, destination: Destination? = null, ) = Intent(context, MainActivity::class.java).apply { - putExtra(EXTRA_START_DESTINATION, destination) + destination?.let { putExtra(EXTRA_START_DESTINATION, Json.encodeToString(it)) } } } @@ -70,9 +72,8 @@ class MainActivity : BaseActivity(), MainVie override val currentStackSize get() = navController.currentStack?.size override val currentViewTitle - get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let { - getString(it) - } + get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId + ?.let { getString(it) } override val currentViewSubtitle get() = (navController.currentFrag as? MainView.TitledView)?.subtitleString @@ -86,7 +87,7 @@ class MainActivity : BaseActivity(), MainVie messageContainer = binding.mainMessageContainer updateHelper.messageContainer = binding.mainFragmentContainer - val destination = (intent.getParcelableExtra(EXTRA_START_DESTINATION) as Destination?) + val destination = intent.getStringExtra(EXTRA_START_DESTINATION) ?.takeIf { savedInstanceState == null } presenter.onAttachView(this, destination) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index cb414fcbc..a07bdb371 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -19,6 +19,8 @@ import io.github.wulkanowy.ui.modules.message.MessageView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.utils.AnalyticsHelper +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import timber.log.Timber import java.time.Duration import java.time.Instant @@ -30,6 +32,7 @@ class MainPresenter @Inject constructor( private val prefRepository: PreferencesRepository, private val syncManager: SyncManager, private val analytics: AnalyticsHelper, + private val json: Json ) : BasePresenter(errorHandler, studentRepository) { private var studentsWitSemesters: List? = null @@ -51,9 +54,11 @@ class MainPresenter @Inject constructor( else -> 4 } - fun onAttachView(view: MainView, initDestination: Destination?) { + fun onAttachView(view: MainView, initDestinationJson: String?) { super.onAttachView(view) + val initDestination: Destination? = initDestinationJson?.let { json.decodeFromString(it) } + val startMenuIndex = initDestination.startMenuIndex val destinations = rootDestinationTypeList.map { if (it == initDestination?.destinationType) initDestination else it.defaultDestination diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt index 24347e735..cfb628496 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt @@ -15,6 +15,8 @@ import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.utils.openInternetBrowser +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import javax.inject.Inject @SuppressLint("CustomSplashScreen") @@ -29,13 +31,13 @@ class SplashActivity : BaseActivity(), SplashView companion object { - private const val EXTRA_START_DESTINATION = "start_destination" + private const val EXTRA_START_DESTINATION = "start_destination_json" private const val EXTRA_EXTERNAL_URL = "external_url" fun getStartIntent(context: Context, destination: Destination? = null) = Intent(context, SplashActivity::class.java).apply { - putExtra(EXTRA_START_DESTINATION, destination) + destination?.let { putExtra(EXTRA_START_DESTINATION, Json.encodeToString(it)) } flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } } @@ -43,12 +45,12 @@ class SplashActivity : BaseActivity(), SplashView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) installSplashScreen().setKeepOnScreenCondition { true } + shortcutsHelper.initializeShortcuts() val externalLink = intent?.getStringExtra(EXTRA_EXTERNAL_URL) - val startDestination = intent?.getParcelableExtra(EXTRA_START_DESTINATION) as Destination? - ?: shortcutsHelper.getDestination(intent) + val startDestinationJson = intent?.getStringExtra(EXTRA_START_DESTINATION) - presenter.onAttachView(this, externalLink, startDestination) + presenter.onAttachView(this, externalLink, startDestinationJson) } override fun openLoginView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt index 0b7409020..767c885c1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt @@ -5,16 +5,21 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.Destination import kotlinx.coroutines.launch +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import javax.inject.Inject class SplashPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, + private val json: Json ) : BasePresenter(errorHandler, studentRepository) { - fun onAttachView(view: SplashView, externalUrl: String?, startDestination: Destination?) { + fun onAttachView(view: SplashView, externalUrl: String?, startDestinationJson: String?) { super.onAttachView(view) + val startDestination: Destination? = startDestinationJson?.let { json.decodeFromString(it) } + if (!externalUrl.isNullOrBlank()) { view.openExternalUrlAndFinish(externalUrl) return diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt index 7557d745a..720239e62 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt @@ -5,13 +5,9 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.clearMocks -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.just -import io.mockk.verify +import kotlinx.serialization.json.Json import org.junit.Before import org.junit.Test @@ -43,8 +39,14 @@ class MainPresenterTest { clearMocks(mainView) every { mainView.initView(any(), any()) } just Runs - presenter = - MainPresenter(errorHandler, studentRepository, prefRepository, syncManager, analytics) + presenter = MainPresenter( + errorHandler = errorHandler, + studentRepository = studentRepository, + prefRepository = prefRepository, + syncManager = syncManager, + analytics = analytics, + json = Json + ) presenter.onAttachView(mainView, null) } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt index 323119742..eb362978d 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt @@ -7,6 +7,7 @@ import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.impl.annotations.MockK import io.mockk.verify +import kotlinx.serialization.json.Json import org.junit.Before import org.junit.Rule import org.junit.Test @@ -30,7 +31,7 @@ class SplashPresenterTest { @Before fun setUp() { MockKAnnotations.init(this) - presenter = SplashPresenter(errorHandler, studentRepository) + presenter = SplashPresenter(errorHandler, studentRepository, Json) } @Test From d37de197fca0a5f364dbcd8989056afd1b675987 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 14:52:06 +0000 Subject: [PATCH 103/105] Bump firebase-bom from 29.3.0 to 29.3.1 (#1836) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 30c81b55b..c8a96abf2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -233,7 +233,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.6.0' - playImplementation platform('com.google.firebase:firebase-bom:29.3.0') + playImplementation platform('com.google.firebase:firebase-bom:29.3.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 426bee882c8d4aac502b99bdbee24c5ca527c2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 18 Apr 2022 16:52:28 +0200 Subject: [PATCH 104/105] Display timetable header as HTML on dashboard tile (#1835) --- .../github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3b6dc7298..9191d43c9 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 @@ -9,6 +9,7 @@ import android.os.Looper import android.view.LayoutInflater import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.text.parseAsHtml import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updateMarginsRelative @@ -563,7 +564,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter Date: Tue, 19 Apr 2022 09:56:01 +0200 Subject: [PATCH 105/105] Version 1.6.3 --- app/build.gradle | 4 ++-- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c8a96abf2..188af355d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,8 +22,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 31 - versionCode 106 - versionName "1.6.2" + versionCode 107 + versionName "1.6.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" 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 4a3b1e2f6..2dba56ddd 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,4 +1,4 @@ -Wersja 1.6.2 +Wersja 1.6.3 - dodaliśmy możliwość usuwania wielu wiadomości jednocześnie - dodaliśmy opcję szybkiego dodawania sprawdzianów do kalendarza