diff --git a/.github/workflows/deploy-store.yml b/.github/workflows/deploy-store.yml
index cfb0fb523..3ce618ca7 100644
--- a/.github/workflows/deploy-store.yml
+++ b/.github/workflows/deploy-store.yml
@@ -38,6 +38,7 @@ jobs:
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
+ DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 3def08953..7f8591bb1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,10 +2,12 @@ name: Tests
on:
push:
- branches: [ master, develop ]
+ branches:
+ - master
+ - develop
+ - 'hotfix/**'
tags: [ '*' ]
pull_request:
- branches: [ master, develop ]
jobs:
diff --git a/LICENSE b/LICENSE
index c97032f74..a1fc37058 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2022 Wulkanowy
+ Copyright 2023 Wulkanowy
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.cs.md b/README.cs.md
index 5c1e5ea71..8171b27d6 100644
--- a/README.cs.md
+++ b/README.cs.md
@@ -1,18 +1,13 @@
-[English version of README](README.en.md)
-
-[Deutsche Version von README](README.de.md)
-
-[Polska wersja README](README.md)
-
-[Slovenská verzia README](README.sk.md)
+Česká verze / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
# Wulkanowy
-[](https://github.com/wulkanowy/wulkanowy/actions)
+[](https://github.com/wulkanowy/wulkanowy/actions)
[](https://codecov.io/gh/wulkanowy/wulkanowy)
[](https://discord.gg/vccAQBr)
[](https://f-droid.org/packages/io.github.wulkanowy/)
[](https://github.com/wulkanowy/wulkanowy/releases)
+[](https://translate.wulkanowy.net.pl)
Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
@@ -39,7 +34,7 @@ Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
* podpora více účtů s možností přejmenování žáků
* tmavý a černý (AMOLED) motiv
* offline režim
-* žádné reklamy
+* volitelné reklamy na podporu projektu
## Stáhnout
@@ -57,7 +52,7 @@ Aktuální verzi si můžete stáhnout z Google Play, F-Droid nebo Huawei AppGal
Můžete si také stáhnout [vývojovou verzi](https://wulkanowy.github.io/#download), která zahrnuje nové funkce připravované pro příští vydání
-## Postaveno s
+## Postaveno s pomocí
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
diff --git a/README.de.md b/README.de.md
index b9e1d1ec1..972f66ba9 100644
--- a/README.de.md
+++ b/README.de.md
@@ -1,14 +1,13 @@
-[Polska wersja README](README.md)
-
-[English version of README](README.en.md)
+[Česká verze](README.cs.md) / Deutsche Version / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
# Wulkanowy
-[](https://github.com/wulkanowy/wulkanowy/actions)
+[](https://github.com/wulkanowy/wulkanowy/actions)
[](https://codecov.io/gh/wulkanowy/wulkanowy)
[](https://discord.gg/vccAQBr)
[](https://f-droid.org/packages/io.github.wulkanowy/)
[](https://github.com/wulkanowy/wulkanowy/releases)
+[](https://translate.wulkanowy.net.pl)
Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern
@@ -22,7 +21,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre
* Prozentsatz der Anwesenheit
* Prüfungen
* Stundenplan
- * Unterricht abgeschlossen
+ * abgeschlossene Unterrichtsstunden
* Nachrichten
* Hausaufgaben
* Anmerkungen
@@ -35,7 +34,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre
* Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern
* dunkles und schwarzes (AMOLED) Thema
* Offline-Modus
-* keine Werbung
+* optionale Werbungen, die es uns ermöglichen das Projekt zu unterstützen
## Herunterladen
diff --git a/README.en.md b/README.en.md
index 1ac2a6721..6e4da4637 100644
--- a/README.en.md
+++ b/README.en.md
@@ -1,18 +1,13 @@
-[Polska wersja README](README.md)
-
-[Deutsche Version von README](README.de.md)
-
-[Česká verze README](README.cs.md)
-
-[Slovenská verzia README](README.sk.md)
+[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / English version / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
# Wulkanowy
-[](https://github.com/wulkanowy/wulkanowy/actions)
+[](https://github.com/wulkanowy/wulkanowy/actions)
[](https://codecov.io/gh/wulkanowy/wulkanowy)
[](https://discord.gg/vccAQBr)
[](https://f-droid.org/packages/io.github.wulkanowy/)
[](https://github.com/wulkanowy/wulkanowy/releases)
+[](https://translate.wulkanowy.net.pl)
Unofficial android VULCAN UONET+ register client for both students and their parents
@@ -39,7 +34,7 @@ Unofficial android VULCAN UONET+ register client for both students and their par
* support for multiple accounts with the ability to rename students
* dark and black (AMOLED) theme
* offline mode
-* no ads
+* optional ads which allow to support the project
## Download
diff --git a/README.md b/README.md
index e7c7d4c5e..f3d2e29a2 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,13 @@
-[English version of README](README.en.md)
-
-[Deutsche Version von README](README.de.md)
-
-[Česká verze README](README.cs.md)
-
-[Slovenská verzia README](README.sk.md)
+[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / Polska wersja / [Slovenská verzia](README.sk.md)
# Wulkanowy
-[](https://github.com/wulkanowy/wulkanowy/actions)
+[](https://github.com/wulkanowy/wulkanowy/actions)
[](https://codecov.io/gh/wulkanowy/wulkanowy)
[](https://discord.gg/vccAQBr)
[](https://f-droid.org/packages/io.github.wulkanowy/)
[](https://github.com/wulkanowy/wulkanowy/releases)
+[](https://translate.wulkanowy.net.pl)
Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
@@ -39,7 +34,7 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
* obsługa wielu kont wraz z możliwością zmiany nazwy ucznia
* ciemny i czarny (AMOLED) motyw
* tryb offline
-* brak reklam
+* opcjonalne reklamy umożliwiające wsparcie projektu
## Pobierz
diff --git a/README.sk.md b/README.sk.md
index 2f3ba41dd..ff0c6e3c9 100644
--- a/README.sk.md
+++ b/README.sk.md
@@ -1,18 +1,13 @@
-[English version of README](README.en.md)
-
-[Deutsche Version von README](README.de.md)
-
-[Polska wersja README](README.md)
-
-[Česká verze README](README.cs.md)
+[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / Slovenská verzia
# Wulkanowy
-[](https://github.com/wulkanowy/wulkanowy/actions)
+[](https://github.com/wulkanowy/wulkanowy/actions)
[](https://codecov.io/gh/wulkanowy/wulkanowy)
[](https://discord.gg/vccAQBr)
[](https://f-droid.org/packages/io.github.wulkanowy/)
[](https://github.com/wulkanowy/wulkanowy/releases)
+[](https://translate.wulkanowy.net.pl)
Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
@@ -39,7 +34,7 @@ Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
* podpora viacerých účtov s možnosťou premenovania žiakov
* tmavý a čierny (AMOLED) motív
* offline režim
-* žiadne reklamy
+* voliteľné reklamy na podporu projektu
## Stiahnuť
@@ -57,7 +52,7 @@ Aktuálnu verziu si môžete stiahnuť z Google Play, F-Droid alebo Huawei AppGa
Môžete si tiež stiahnuť [vývojovú verziu](https://wulkanowy.github.io/#download), ktorá zahrňuje nové funkcie pripravované pre budúce vydanie
-## Postavené s
+## Postavené s pomocou
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
diff --git a/app/build.gradle b/app/build.gradle
index e99e8773f..255e0098a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,15 +16,15 @@ apply from: 'hooks.gradle'
android {
namespace 'io.github.wulkanowy'
- compileSdkVersion 32
+ compileSdkVersion 33
defaultConfig {
applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
- targetSdkVersion 32
- versionCode 109
- versionName "1.7.0"
+ targetSdkVersion 33
+ versionCode 121
+ versionName "1.9.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@@ -161,8 +161,8 @@ play {
defaultToAppBundles = false
track = 'production'
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
- userFraction = 0.05d
- updatePriority = 5
+ userFraction = 0.50d
+ updatePriority = 2
enabled.set(false)
}
@@ -181,24 +181,24 @@ ext {
android_hilt = "1.0.0"
room = "2.4.3"
chucker = "3.5.2"
- mockk = "1.12.5"
+ mockk = "1.13.3"
coroutines = "1.6.4"
}
dependencies {
- implementation "io.github.wulkanowy:sdk:1.7.0"
+ implementation "io.github.wulkanowy:sdk:1.9.2"
- coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
- implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
- implementation "androidx.core:core-ktx:1.8.0"
+ implementation "androidx.core:core-ktx:1.9.0"
implementation 'androidx.core:core-splashscreen:1.0.0'
- implementation "androidx.activity:activity-ktx:1.5.1"
- implementation "androidx.appcompat:appcompat:1.5.0"
- implementation "androidx.fragment:fragment-ktx:1.5.2"
- implementation "androidx.annotation:annotation:1.4.0"
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ implementation "androidx.appcompat:appcompat:1.5.1"
+ implementation "androidx.fragment:fragment-ktx:1.5.5"
+ implementation "androidx.annotation:annotation:1.5.0"
implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.recyclerview:recyclerview:1.2.1"
@@ -206,10 +206,10 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
- implementation "com.google.android.material:material:1.6.1"
+ implementation "com.google.android.material:material:1.7.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'
+ implementation 'com.github.lopspower:CircularImageView:4.3.0'
implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager"
@@ -236,21 +236,23 @@ dependencies {
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
- implementation "io.coil-kt:coil:2.2.0"
- implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
+ implementation "io.coil-kt:coil:2.2.2"
+ implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
implementation 'com.fredporciuncula:flow-preferences:1.8.0'
+ implementation 'org.apache.commons:commons-text:1.10.0'
- playImplementation platform('com.google.firebase:firebase-bom:30.3.2')
+ playImplementation platform('com.google.firebase:firebase-bom:31.1.1')
playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation 'com.google.firebase:firebase-crashlytics:'
+ playImplementation 'com.google.firebase:firebase-config-ktx'
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:21.1.0'
+ playImplementation 'com.google.android.gms:play-services-ads:21.4.0'
- hmsImplementation 'com.huawei.hms:hianalytics:6.7.0.300'
- hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.1.300'
+ hmsImplementation 'com.huawei.hms:hianalytics:6.9.0.301'
+ hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.302'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
@@ -263,17 +265,17 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
- testImplementation 'org.robolectric:robolectric:4.8.1'
- testImplementation "androidx.test:runner:1.4.0"
- testImplementation "androidx.test.ext:junit:1.1.3"
- testImplementation "androidx.test:core:1.4.0"
+ testImplementation 'org.robolectric:robolectric:4.9.2'
+ testImplementation "androidx.test:runner:1.5.1"
+ testImplementation "androidx.test.ext:junit:1.1.4"
+ testImplementation "androidx.test:core:1.5.0"
testImplementation "androidx.room:room-testing:$room"
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
- androidTestImplementation "androidx.test:core:1.4.0"
- androidTestImplementation "androidx.test:runner:1.4.0"
- androidTestImplementation "androidx.test.ext:junit:1.1.3"
+ androidTestImplementation "androidx.test:core:1.5.0"
+ androidTestImplementation "androidx.test:runner:1.5.1"
+ androidTestImplementation "androidx.test.ext:junit:1.1.4"
androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
}
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json
new file mode 100644
index 000000000..129d1917b
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json
@@ -0,0 +1,2421 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 52,
+ "identityHash": "8742176f26afcc81279d4a073dca2949",
+ "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}` (`message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mailboxKey",
+ "columnName": "mailbox_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondents",
+ "columnName": "correspondents",
+ "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": "readBy",
+ "columnName": "read_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "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": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "recipients",
+ "columnName": "recipients",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageAttachments",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` 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": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "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": "Mailboxes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `userLoginId` INTEGER NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))",
+ "fields": [
+ {
+ "fieldPath": "globalKey",
+ "columnName": "globalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "userLoginId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "studentName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolNameShort",
+ "columnName": "schoolNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "globalKey"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Recipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "mailboxGlobalKey",
+ "columnName": "mailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentMailboxGlobalKey",
+ "columnName": "studentMailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "schoolShortName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "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}` (`user_login_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": "user_login_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}` (`user_login_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": "userLoginId",
+ "columnName": "user_login_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, '8742176f26afcc81279d4a073dca2949')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json
new file mode 100644
index 000000000..985617872
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json
@@ -0,0 +1,2439 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 53,
+ "identityHash": "1dc96a366125ec9f8567da87cdc9c863",
+ "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}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mailboxKey",
+ "columnName": "mailbox_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondents",
+ "columnName": "correspondents",
+ "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": "readBy",
+ "columnName": "read_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "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": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "recipients",
+ "columnName": "recipients",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageAttachments",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` 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": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "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": "Mailboxes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))",
+ "fields": [
+ {
+ "fieldPath": "globalKey",
+ "columnName": "globalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolId",
+ "columnName": "schoolId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "studentName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolNameShort",
+ "columnName": "schoolNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "globalKey"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Recipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "mailboxGlobalKey",
+ "columnName": "mailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentMailboxGlobalKey",
+ "columnName": "studentMailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "schoolShortName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "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}` (`user_login_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": "user_login_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}` (`user_login_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": "userLoginId",
+ "columnName": "user_login_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, '1dc96a366125ec9f8567da87cdc9c863')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json
new file mode 100644
index 000000000..7b41672b9
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json
@@ -0,0 +1,2439 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 54,
+ "identityHash": "1dc96a366125ec9f8567da87cdc9c863",
+ "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}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mailboxKey",
+ "columnName": "mailbox_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondents",
+ "columnName": "correspondents",
+ "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": "readBy",
+ "columnName": "read_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "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": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "recipients",
+ "columnName": "recipients",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageAttachments",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` 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": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "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": "Mailboxes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))",
+ "fields": [
+ {
+ "fieldPath": "globalKey",
+ "columnName": "globalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolId",
+ "columnName": "schoolId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "studentName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolNameShort",
+ "columnName": "schoolNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "globalKey"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Recipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "mailboxGlobalKey",
+ "columnName": "mailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentMailboxGlobalKey",
+ "columnName": "studentMailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "schoolShortName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "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}` (`user_login_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": "user_login_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}` (`user_login_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": "userLoginId",
+ "columnName": "user_login_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, '1dc96a366125ec9f8567da87cdc9c863')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json
new file mode 100644
index 000000000..60c2efbe0
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json
@@ -0,0 +1,2435 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 55,
+ "identityHash": "cba22eea6d26cf4d6b9a388ba3329a12",
+ "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}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mailboxKey",
+ "columnName": "mailbox_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "correspondents",
+ "columnName": "correspondents",
+ "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": "readBy",
+ "columnName": "read_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "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": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "recipients",
+ "columnName": "recipients",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageAttachments",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))",
+ "fields": [
+ {
+ "fieldPath": "messageGlobalKey",
+ "columnName": "message_global_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "filename",
+ "columnName": "filename",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "message_global_key",
+ "url",
+ "filename"
+ ],
+ "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": "Mailboxes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))",
+ "fields": [
+ {
+ "fieldPath": "globalKey",
+ "columnName": "globalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolId",
+ "columnName": "schoolId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "studentName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolNameShort",
+ "columnName": "schoolNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "globalKey"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Recipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "mailboxGlobalKey",
+ "columnName": "mailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentMailboxGlobalKey",
+ "columnName": "studentMailboxGlobalKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "fullName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "userName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "schoolShortName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "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}` (`user_login_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": "user_login_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}` (`user_login_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": "userLoginId",
+ "columnName": "user_login_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, 'cba22eea6d26cf4d6b9a388ba3329a12')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
index 7dbec2cb9..b7b756b9e 100644
--- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,4 +2,5 @@
-
\ No newline at end of file
+
+
diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 7dbec2cb9..000000000
--- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 81e723ecc..000000000
Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index 394b57076..000000000
Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 365b4d663..000000000
Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 463c089b3..000000000
Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 53d6f5bbd..000000000
Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
index 3bf7e1693..a3eed484a 100644
--- a/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
+++ b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
@@ -8,15 +8,7 @@ import javax.inject.Singleton
@Suppress("UNUSED_PARAMETER")
class AnalyticsHelper @Inject constructor() {
- fun logEvent(name: String, vararg params: Pair) {
- // do nothing
- }
-
- fun setCurrentScreen(activity: Activity, name: String?) {
- // do nothing
- }
-
- fun popCurrentScreen(name: String?) {
- // do nothing
- }
+ fun logEvent(name: String, vararg params: Pair) = Unit
+ fun setCurrentScreen(activity: Activity, name: String?) = Unit
+ fun popCurrentScreen(name: String?) = Unit
}
diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt
new file mode 100644
index 000000000..88f2598f8
--- /dev/null
+++ b/app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt
@@ -0,0 +1,7 @@
+package io.github.wulkanowy.utils
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class RemoteConfigHelper @Inject constructor() : BaseRemoteConfigHelper()
diff --git a/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
index 5d33825f1..1f78931ae 100644
--- a/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
+++ b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
@@ -3,26 +3,38 @@ package io.github.wulkanowy.utils
import android.app.Activity
import android.content.Context
import android.os.Bundle
+import com.huawei.agconnect.crash.AGConnectCrash
import com.huawei.hms.analytics.HiAnalytics
import dagger.hilt.android.qualifiers.ApplicationContext
+import io.github.wulkanowy.data.repositories.PreferencesRepository
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AnalyticsHelper @Inject constructor(
- @ApplicationContext private val context: Context
+ @ApplicationContext private val context: Context,
+ preferencesRepository: PreferencesRepository,
+ appInfo: AppInfo,
) {
private val analytics by lazy { HiAnalytics.getInstance(context) }
+ private val connectCrash by lazy { AGConnectCrash.getInstance() }
+
+ init {
+ if (!appInfo.isDebug) {
+ connectCrash.setUserId(preferencesRepository.installationId)
+ }
+ }
+
fun logEvent(name: String, vararg params: Pair) {
Bundle().apply {
- params.forEach {
- if (it.second == null) return@forEach
- when (it.second) {
- is String, is String? -> putString(it.first, it.second as String)
- is Int, is Int? -> putInt(it.first, it.second as Int)
- is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
+ params.forEach { (key, value) ->
+ if (value == null) return@forEach
+ when (value) {
+ is String -> putString(key, value)
+ is Int -> putInt(key, value)
+ is Boolean -> putBoolean(key, value)
}
}
analytics.onEvent(name, this)
diff --git a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt
index b0c34f413..377e83666 100644
--- a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt
+++ b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
import android.util.Log
import com.huawei.agconnect.crash.AGConnectCrash
import fr.bipi.tressence.base.FormatterPriorityTree
+import fr.bipi.tressence.common.StackTraceRecorder
class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
@@ -22,16 +23,10 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter)
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return
- // Disabled due to a bug in the Huawei library
-
- /*connectCrash.setCustomKey("priority", priority)
- connectCrash.setCustomKey("tag", tag.orEmpty())
- connectCrash.setCustomKey("message", message)
-
if (t != null) {
connectCrash.recordException(t)
} else {
connectCrash.recordException(StackTraceRecorder(format(priority, tag, message)))
- }*/
+ }
}
}
diff --git a/app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt
new file mode 100644
index 000000000..88f2598f8
--- /dev/null
+++ b/app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt
@@ -0,0 +1,7 @@
+package io.github.wulkanowy.utils
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class RemoteConfigHelper @Inject constructor() : BaseRemoteConfigHelper()
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7835db902..3773093b2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,7 +8,8 @@
-
+
+
@@ -36,13 +37,14 @@
+ tools:ignore="DataExtractionRules,UnusedAttribute">
Resource.mapData(block: (T) -> U) = when (this) {
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.Loading -> "started"
is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else ""
is Resource.Error -> "exception occurred: ${it.error}"
}
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 15b38805b..0aea86dab 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
@@ -47,6 +47,8 @@ import javax.inject.Singleton
AutoMigration(from = 44, to = 45),
AutoMigration(from = 46, to = 47),
AutoMigration(from = 47, to = 48),
+ AutoMigration(from = 51, to = 52),
+ AutoMigration(from = 54, to = 55, spec = Migration55::class),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@@ -55,7 +57,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
- const val VERSION_SCHEMA = 51
+ const val VERSION_SCHEMA = 55
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@@ -105,6 +107,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration49(),
Migration50(),
Migration51(),
+ Migration53(),
+ Migration54(),
)
fun newInstance(
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt
index 048e9e3cd..056a5cbd1 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt
@@ -2,11 +2,12 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Delete
import androidx.room.Insert
+import androidx.room.OnConflictStrategy
import androidx.room.Update
interface BaseDao {
- @Insert
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(items: List): List
@Update
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt
index 8589db311..084192a07 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt
@@ -3,15 +3,16 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Mailbox
+import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Singleton
@Dao
interface MailboxDao : BaseDao {
- @Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId ")
- suspend fun loadAll(userLoginId: Int): List
+ @Query("SELECT * FROM Mailboxes WHERE email = :email")
+ suspend fun loadAll(email: String): List
- @Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId AND studentName = :studentName ")
- suspend fun load(userLoginId: Int, studentName: String): Mailbox?
+ @Query("SELECT * FROM Mailboxes WHERE email = :email AND symbol = :symbol AND schoolId = :schoolId")
+ fun loadAll(email: String, symbol: String, schoolId: String): Flow>
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt
index 8c730c9bc..1709f7636 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt
@@ -16,4 +16,7 @@ interface MessagesDao : BaseDao {
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
fun loadAll(mailboxKey: String, folder: Int): Flow>
+
+ @Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
+ fun loadAll(folder: Int, email: String): Flow>
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt
index 5e6eec668..b4b7379f2 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt
@@ -13,4 +13,7 @@ interface TimetableDao : BaseDao {
@Query("SELECT * FROM Timetable 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>
+
+ @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
+ fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt
index 7c08e481d..e65e213dd 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt
@@ -1,20 +1,27 @@
package io.github.wulkanowy.data.db.entities
+import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
+import kotlinx.parcelize.Parcelize
+@Parcelize
@Entity(tableName = "Mailboxes")
data class Mailbox(
@PrimaryKey
val globalKey: String,
+
+ val email: String,
+ val symbol: String,
+ val schoolId: String,
+
val fullName: String,
val userName: String,
- val userLoginId: Int,
val studentName: String,
val schoolNameShort: String,
val type: MailboxType,
-)
+) : java.io.Serializable, Parcelable
enum class MailboxType {
STUDENT,
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 77874e03d..d1356b33d 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
@@ -9,6 +9,9 @@ import java.time.Instant
@Entity(tableName = "Messages")
data class Message(
+ @ColumnInfo(name = "email")
+ val email: String,
+
@ColumnInfo(name = "message_global_key")
val messageGlobalKey: String,
@@ -29,6 +32,12 @@ data class Message(
var unread: Boolean,
+ @ColumnInfo(name = "read_by")
+ val readBy: Int?,
+
+ @ColumnInfo(name = "unread_by")
+ val unreadBy: Int?,
+
@ColumnInfo(name = "has_attachments")
val hasAttachments: Boolean
) : Serializable {
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt
index 93f042999..6f0c84ad7 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt
@@ -2,16 +2,14 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
-import androidx.room.PrimaryKey
import java.io.Serializable
-@Entity(tableName = "MessageAttachments")
+@Entity(
+ tableName = "MessageAttachments",
+ primaryKeys = ["message_global_key", "url", "filename"],
+)
data class MessageAttachment(
- @PrimaryKey
- @ColumnInfo(name = "real_id")
- val realId: Int,
-
@ColumnInfo(name = "message_global_key")
val messageGlobalKey: String,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt
new file mode 100644
index 000000000..12624a51a
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt
@@ -0,0 +1,57 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+class Migration53 : Migration(52, 53) {
+
+ override fun migrate(database: SupportSQLiteDatabase) {
+ createMailboxTable(database)
+ recreateMessagesTable(database)
+ }
+
+ private fun createMailboxTable(database: SupportSQLiteDatabase) {
+ database.execSQL("DROP TABLE IF EXISTS Mailboxes")
+ database.execSQL(
+ """
+ CREATE TABLE IF NOT EXISTS `Mailboxes` (
+ `globalKey` TEXT NOT NULL,
+ `email` TEXT NOT NULL,
+ `symbol` TEXT NOT NULL,
+ `schoolId` TEXT NOT NULL,
+ `fullName` TEXT NOT NULL,
+ `userName` TEXT NOT NULL,
+ `studentName` TEXT NOT NULL,
+ `schoolNameShort` TEXT NOT NULL,
+ `type` TEXT NOT NULL,
+ PRIMARY KEY(`globalKey`)
+ )""".trimIndent()
+ )
+ }
+
+ private fun recreateMessagesTable(database: SupportSQLiteDatabase) {
+ database.execSQL("DROP TABLE IF EXISTS Messages")
+ database.execSQL(
+ """
+ CREATE TABLE IF NOT EXISTS `Messages` (
+ `email` TEXT NOT NULL,
+ `message_global_key` TEXT NOT NULL,
+ `mailbox_key` TEXT NOT NULL,
+ `message_id` INTEGER NOT NULL,
+ `correspondents` TEXT NOT NULL,
+ `subject` TEXT NOT NULL,
+ `date` INTEGER NOT NULL,
+ `folder_id` INTEGER NOT NULL,
+ `unread` INTEGER NOT NULL,
+ `read_by` INTEGER,
+ `unread_by` INTEGER,
+ `has_attachments` INTEGER NOT NULL,
+ `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `is_notified` INTEGER NOT NULL,
+ `content` TEXT NOT NULL,
+ `sender` TEXT,
+ `recipients` TEXT
+ )""".trimIndent()
+ )
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt
new file mode 100644
index 000000000..678bd32f2
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt
@@ -0,0 +1,26 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+class Migration54 : Migration(53, 54) {
+
+ override fun migrate(database: SupportSQLiteDatabase) {
+ migrateResman(database)
+ removeTomaszowMazowieckiStudents(database)
+ }
+
+ private fun migrateResman(database: SupportSQLiteDatabase) {
+ database.execSQL("""
+ UPDATE Students SET
+ scrapper_base_url = 'https://vulcan.net.pl',
+ login_type = 'ADFSLightScoped',
+ symbol = 'rzeszowprojekt'
+ WHERE scrapper_base_url = 'https://resman.pl'
+ """.trimIndent())
+ }
+
+ private fun removeTomaszowMazowieckiStudents(database: SupportSQLiteDatabase) {
+ database.execSQL("DELETE FROM Students WHERE symbol = 'tomaszowmazowiecki'")
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt
new file mode 100644
index 000000000..424be171b
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt
@@ -0,0 +1,17 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.DeleteColumn
+import androidx.room.migration.AutoMigrationSpec
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+@DeleteColumn(
+ tableName = "MessageAttachments",
+ columnName = "real_id",
+)
+class Migration55 : AutoMigrationSpec {
+
+ override fun onPostMigrate(db: SupportSQLiteDatabase) {
+ db.execSQL("DELETE FROM Messages")
+ db.execSQL("DELETE FROM MessageAttachments")
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt
index 46e67fdaa..c0ed0c8c2 100644
--- a/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt
@@ -3,17 +3,22 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance
import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary
-fun List.mapToEntities(semester: Semester) = map {
+fun List.mapToEntities(semester: Semester, lessons: List) = map {
Attendance(
studentId = semester.studentId,
diaryId = semester.diaryId,
date = it.date,
timeId = it.timeId,
number = it.number,
- subject = it.subject,
+ subject = it.subject.ifBlank {
+ lessons.find { lesson ->
+ lesson.date == it.date && lesson.number == it.number
+ }?.subject.orEmpty()
+ },
name = it.name,
presence = it.presence,
absence = it.absence,
diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt
index 2ccca1b90..0cf547770 100644
--- a/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt
@@ -10,9 +10,11 @@ fun List.mapToEntities(student: Student) = map {
globalKey = it.globalKey,
fullName = it.fullName,
userName = it.userName,
- userLoginId = student.userLoginId,
studentName = it.studentName,
schoolNameShort = it.schoolNameShort,
type = MailboxType.valueOf(it.type.name),
+ email = student.email,
+ symbol = student.symbol,
+ schoolId = student.schoolSymbol,
)
}
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 2e7967f0e..6fc5dc950 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
@@ -2,21 +2,36 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.sdk.pojo.MailboxType
+import timber.log.Timber
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
-fun List.mapToEntities(mailbox: Mailbox) = map {
+fun List.mapToEntities(
+ student: Student,
+ mailbox: Mailbox?,
+ allMailboxes: List
+): List = map {
Message(
messageGlobalKey = it.globalKey,
- mailboxKey = mailbox.globalKey,
+ mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box ->
+ box.fullName == it.mailbox
+ }?.globalKey.let { mailboxKey ->
+ if (mailboxKey == null) {
+ Timber.e("Can't find ${it.mailbox} in $allMailboxes")
+ "unknown"
+ } else mailboxKey
+ },
+ email = student.email,
messageId = it.id,
correspondents = it.correspondents,
subject = it.subject.trim(),
date = it.dateZoned.toInstant(),
folderId = it.folderId,
unread = it.unread,
- hasAttachments = it.hasAttachments
+ unreadBy = it.unreadBy,
+ readBy = it.readBy,
+ hasAttachments = it.hasAttachments,
).apply {
content = it.content.orEmpty()
}
@@ -25,7 +40,6 @@ fun List.mapToEntities(mailbox: Mailbox) = map {
fun List.mapToEntities(messageGlobalKey: String) = map {
MessageAttachment(
messageGlobalKey = messageGlobalKey,
- realId = it.url.hashCode(),
url = it.url,
filename = it.filename
)
diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt
new file mode 100644
index 000000000..2dfd7e062
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt
@@ -0,0 +1,87 @@
+package io.github.wulkanowy.data.mappers
+
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.pojos.*
+import io.github.wulkanowy.sdk.Sdk
+import io.github.wulkanowy.sdk.mapper.mapSemesters
+import java.time.Instant
+import io.github.wulkanowy.sdk.scrapper.register.RegisterStudent as SdkRegisterStudent
+import io.github.wulkanowy.sdk.scrapper.register.RegisterUser as SdkRegisterUser
+
+fun SdkRegisterUser.mapToPojo(password: String) = RegisterUser(
+ email = email,
+ login = login,
+ password = password,
+ baseUrl = baseUrl,
+ loginType = loginType,
+ symbols = symbols.map { registerSymbol ->
+ RegisterSymbol(
+ symbol = registerSymbol.symbol,
+ error = registerSymbol.error,
+ userName = registerSymbol.userName,
+ schools = registerSymbol.schools.map {
+ RegisterUnit(
+ userLoginId = it.userLoginId,
+ schoolId = it.schoolId,
+ schoolName = it.schoolName,
+ schoolShortName = it.schoolShortName,
+ parentIds = it.parentIds,
+ studentIds = it.studentIds,
+ employeeIds = it.employeeIds,
+ error = it.error,
+ students = it.subjects
+ .filterIsInstance()
+ .map { registerSubject ->
+ RegisterStudent(
+ studentId = registerSubject.studentId,
+ studentName = registerSubject.studentName,
+ studentSecondName = registerSubject.studentSecondName,
+ studentSurname = registerSubject.studentSurname,
+ className = registerSubject.className,
+ classId = registerSubject.classId,
+ isParent = registerSubject.isParent,
+ semesters = registerSubject.semesters
+ .mapSemesters()
+ .mapToEntities(registerSubject.studentId),
+ )
+ },
+ )
+ }
+ )
+ }
+)
+
+fun RegisterStudent.mapToStudentWithSemesters(
+ user: RegisterUser,
+ symbol: RegisterSymbol,
+ unit: RegisterUnit,
+ colors: List,
+): StudentWithSemesters = StudentWithSemesters(
+ semesters = semesters,
+ student = Student(
+ email = user.login, // for compatibility
+ userName = symbol.userName,
+ userLoginId = unit.userLoginId,
+ isParent = isParent,
+ className = className,
+ classId = classId,
+ studentId = studentId,
+ symbol = symbol.symbol,
+ loginType = user.loginType.name,
+ schoolName = unit.schoolName,
+ schoolShortName = unit.schoolShortName,
+ schoolSymbol = unit.schoolId,
+ studentName = "$studentName $studentSurname",
+ loginMode = Sdk.Mode.SCRAPPER.name,
+ scrapperBaseUrl = user.baseUrl,
+ mobileBaseUrl = "",
+ certificateKey = "",
+ privateKey = "",
+ password = user.password,
+ isCurrent = false,
+ registrationDate = Instant.now(),
+ ).apply {
+ avatarColor = colors.random()
+ },
+)
diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt
new file mode 100644
index 000000000..4aea33771
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt
@@ -0,0 +1,43 @@
+package io.github.wulkanowy.data.pojos
+
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.sdk.scrapper.Scrapper
+
+data class RegisterUser(
+ val email: String,
+ val password: String,
+ val login: String, // may be the same as email
+ val baseUrl: String,
+ val loginType: Scrapper.LoginType,
+ val symbols: List,
+) : java.io.Serializable
+
+data class RegisterSymbol(
+ val symbol: String,
+ val error: Throwable?,
+ val userName: String,
+ val schools: List,
+) : java.io.Serializable
+
+data class RegisterUnit(
+ val userLoginId: Int,
+ val schoolId: String,
+ val schoolName: String,
+ val schoolShortName: String,
+ val parentIds: List,
+ val studentIds: List,
+ val employeeIds: List,
+ val error: Throwable?,
+ val students: List,
+) : java.io.Serializable
+
+data class RegisterStudent(
+ val studentId: Int,
+ val studentName: String,
+ val studentSecondName: String,
+ val studentSurname: String,
+ val className: String,
+ val classId: Int,
+ val isParent: Boolean,
+ val semesters: List,
+) : java.io.Serializable
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 9aa6562a6..fd5d8bd16 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
@@ -1,6 +1,7 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.AttendanceDao
+import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
@@ -9,8 +10,10 @@ import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.utils.*
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.withContext
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
@@ -20,6 +23,7 @@ import javax.inject.Singleton
@Singleton
class AttendanceRepository @Inject constructor(
private val attendanceDb: AttendanceDao,
+ private val timetableDb: TimetableDao,
private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -48,10 +52,15 @@ class AttendanceRepository @Inject constructor(
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
},
fetch = {
+ val lessons = withContext(Dispatchers.IO) {
+ timetableDb.load(
+ semester.diaryId, semester.studentId, start.monday, end.sunday
+ )
+ }
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendance(start.monday, end.sunday, semester.semesterId)
- .mapToEntities(semester)
+ .mapToEntities(semester, lessons)
},
saveFetchResult = { old, new ->
attendanceDb.deleteAll(old uniqueSubtract new)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MailboxRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MailboxRepository.kt
deleted file mode 100644
index 7f5974920..000000000
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/MailboxRepository.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package io.github.wulkanowy.data.repositories
-
-import io.github.wulkanowy.data.db.dao.MailboxDao
-import io.github.wulkanowy.data.db.entities.Mailbox
-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.uniqueSubtract
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class MailboxRepository @Inject constructor(
- private val mailboxDao: MailboxDao,
- private val sdk: Sdk,
- private val refreshHelper: AutoRefreshHelper,
-) {
- private val cacheKey = "mailboxes"
-
- suspend fun refreshMailboxes(student: Student) {
- val new = sdk.init(student).getMailboxes().mapToEntities(student)
- val old = mailboxDao.loadAll(student.userLoginId)
-
- mailboxDao.deleteAll(old uniqueSubtract new)
- mailboxDao.insertAll(new uniqueSubtract old)
-
- refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
- }
-
- suspend fun getMailbox(student: Student): Mailbox {
- val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
- val mailbox = mailboxDao.load(student.userLoginId, student.studentName)
-
- return if (isExpired || mailbox == null) {
- refreshMailboxes(student)
- val newMailbox = mailboxDao.load(student.userLoginId, student.studentName)
-
- requireNotNull(newMailbox) {
- "Mailbox for ${student.userName} - ${student.studentName} not found!"
- }
-
- newMailbox
- } else mailbox
- }
-}
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 00cbffb84..53d9beadd 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
@@ -3,8 +3,9 @@ package io.github.wulkanowy.data.repositories
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.SharedPrefProvider
+import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.*
@@ -13,8 +14,8 @@ import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
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.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.utils.AutoRefreshHelper
@@ -40,16 +41,18 @@ class MessageRepository @Inject constructor(
private val refreshHelper: AutoRefreshHelper,
private val sharedPrefProvider: SharedPrefProvider,
private val json: Json,
+ private val mailboxDao: MailboxDao,
+ private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
) {
private val saveFetchResultMutex = Mutex()
- private val cacheKey = "message"
+ private val messagesCacheKey = "message"
+ private val mailboxCacheKey = "mailboxes"
- @Suppress("UNUSED_PARAMETER")
fun getMessages(
student: Student,
- mailbox: Mailbox,
+ mailbox: Mailbox?,
folder: MessageFolder,
forceRefresh: Boolean,
notify: Boolean = false,
@@ -58,13 +61,20 @@ class MessageRepository @Inject constructor(
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
- key = getRefreshKey(cacheKey, student, folder)
+ key = getRefreshKey(messagesCacheKey, mailbox, folder)
)
it.isEmpty() || forceRefresh || isExpired
},
- query = { messagesDb.loadAll(mailbox.globalKey, folder.id) },
+ query = {
+ if (mailbox == null) {
+ messagesDb.loadAll(folder.id, student.email)
+ } else messagesDb.loadAll(mailbox.globalKey, folder.id)
+ },
fetch = {
- sdk.init(student).getMessages(Folder.valueOf(folder.name)).mapToEntities(mailbox)
+ sdk.init(student).getMessages(
+ folder = Folder.valueOf(folder.name),
+ mailboxKey = mailbox?.globalKey,
+ ).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
},
saveFetchResult = { old, new ->
messagesDb.deleteAll(old uniqueSubtract new)
@@ -72,7 +82,9 @@ class MessageRepository @Inject constructor(
it.isNotified = !notify
})
- refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder))
+ refreshHelper.updateLastRefreshTimestamp(
+ getRefreshKey(messagesCacheKey, mailbox, folder)
+ )
}
)
@@ -87,9 +99,14 @@ class MessageRepository @Inject constructor(
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
it.message.unread || it.message.content.isBlank()
},
- query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
+ query = {
+ messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
+ },
fetch = {
- sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey)
+ sdk.init(student).getMessageDetails(
+ messageKey = it!!.message.messageGlobalKey,
+ markAsRead = message.unread && markAsRead,
+ )
},
saveFetchResult = { old, new ->
checkNotNull(old) { "Fetched message no longer exist!" }
@@ -98,7 +115,7 @@ class MessageRepository @Inject constructor(
id = message.id
unread = !markAsRead
sender = new.sender
- recipients = new.recipients.firstOrNull() ?: "Wielu adresoatów"
+ recipients = new.recipients.singleOrNull() ?: "Wielu adresatów"
content = content.ifBlank { new.content }
})
)
@@ -110,8 +127,10 @@ class MessageRepository @Inject constructor(
}
)
- fun getMessagesFromDatabase(mailbox: Mailbox): Flow> {
- return messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
+ fun getMessagesFromDatabase(student: Student, mailbox: Mailbox?): Flow> {
+ return if (mailbox == null) {
+ messagesDb.loadAll(RECEIVED.id, student.email)
+ } else messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
}
suspend fun updateMessages(messages: List) {
@@ -133,7 +152,7 @@ class MessageRepository @Inject constructor(
)
}
- suspend fun deleteMessages(student: Student, mailbox: Mailbox, messages: List) {
+ suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List) {
val firstMessage = messages.first()
sdk.init(student).deleteMessages(
messages = messages.map { it.messageGlobalKey },
@@ -162,15 +181,49 @@ class MessageRepository @Inject constructor(
).first()
}
- suspend fun deleteMessage(student: Student, mailbox: Mailbox, message: Message) {
+ suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
deleteMessages(student, mailbox, listOf(message))
}
+ suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
+ mutex = saveFetchResultMutex,
+ isResultEmpty = { it.isEmpty() },
+ shouldFetch = {
+ val isExpired = refreshHelper.shouldBeRefreshed(
+ key = getRefreshKey(mailboxCacheKey, student),
+ )
+ it.isEmpty() || isExpired || forceRefresh
+ },
+ query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
+ fetch = {
+ sdk.init(student).getMailboxes().mapToEntities(student)
+ },
+ saveFetchResult = { old, new ->
+ mailboxDao.deleteAll(old uniqueSubtract new)
+ mailboxDao.insertAll(new uniqueSubtract old)
+
+ refreshHelper.updateLastRefreshTimestamp(getRefreshKey(mailboxCacheKey, student))
+ }
+ )
+
+ suspend fun getMailboxByStudent(student: Student): Mailbox? {
+ val mailbox = getMailboxByStudentUseCase(student)
+
+ return if (mailbox == null) {
+ getMailboxes(student, forceRefresh = true)
+ .onResourceError { throw it }
+ .onResourceSuccess { Timber.i("Found ${it.size} new mailboxes") }
+ .waitForResult()
+
+ getMailboxByStudentUseCase(student)
+ } else mailbox
+ }
+
var draftMessage: MessageDraft?
- get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
+ get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_draft))
?.let { json.decodeFromString(it) }
set(value) = sharedPrefProvider.putString(
- context.getString(R.string.pref_key_message_send_draft),
+ context.getString(R.string.pref_key_message_draft),
value?.let { json.encodeToString(it) }
)
}
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 486538e0c..afc262868 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
@@ -10,17 +10,16 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.*
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
-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.Instant
+import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
-@OptIn(ExperimentalCoroutinesApi::class)
@Singleton
class PreferencesRepository @Inject constructor(
@ApplicationContext val context: Context,
@@ -316,6 +315,16 @@ class PreferencesRepository @Inject constructor(
putBoolean(context.getString(R.string.pref_key_ads_enabled), value)
}
+ var installationId: String
+ get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
+ private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
+
+ init {
+ if (installationId.isEmpty()) {
+ installationId = UUID.randomUUID().toString()
+ }
+ }
+
private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default)
private fun getLong(id: String, default: Int) =
@@ -331,23 +340,14 @@ class PreferencesRepository @Inject constructor(
private fun getBoolean(id: String, default: Int) =
sharedPref.getBoolean(id, context.resources.getBoolean(default))
- private fun getBoolean(id: Int, default: Boolean) =
- sharedPref.getBoolean(context.getString(id), default)
-
private companion object {
-
+ private const val PREF_KEY_INSTALLATION_ID = "installation_id"
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
-
private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count"
-
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
-
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
-
private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown"
-
private const val PREF_KEY_PERSONALIZED_ADS_ENABLED = "personalized_ads_enabled"
-
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt
index e80f028e1..79984ce6d 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt
@@ -33,9 +33,11 @@ class RecipientRepository @Inject constructor(
suspend fun getRecipients(
student: Student,
- mailbox: Mailbox,
- type: MailboxType
+ mailbox: Mailbox?,
+ type: MailboxType,
): List {
+ mailbox ?: return emptyList()
+
val cached = recipientDb.loadAll(type, mailbox.globalKey)
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
@@ -47,11 +49,15 @@ class RecipientRepository @Inject constructor(
suspend fun getMessageSender(
student: Student,
- mailbox: Mailbox,
- message: Message
- ): List = sdk.init(student)
- .getMessageReplayDetails(message.messageGlobalKey)
- .sender
- .let(::listOf)
- .mapToEntities(mailbox.globalKey)
+ mailbox: Mailbox?,
+ message: Message,
+ ): List {
+ mailbox ?: return emptyList()
+
+ return sdk.init(student)
+ .getMessageReplayDetails(message.messageGlobalKey)
+ .sender
+ .let(::listOf)
+ .mapToEntities(mailbox.globalKey)
+ }
}
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 f006b7d28..b1d1ba832 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
@@ -11,6 +11,8 @@ import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.mappers.mapToEntities
+import io.github.wulkanowy.data.mappers.mapToPojo
+import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.DispatchersProvider
@@ -52,6 +54,14 @@ class StudentRepository @Inject constructor(
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
.mapToEntities(password, appInfo.defaultColorsForAvatar)
+ suspend fun getUserSubjectsFromScrapper(
+ email: String,
+ password: String,
+ scrapperBaseUrl: String,
+ symbol: String
+ ): RegisterUser = sdk.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol)
+ .mapToPojo(password)
+
suspend fun getStudentsHybrid(
email: String,
password: String,
diff --git a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt
new file mode 100644
index 000000000..669514aae
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt
@@ -0,0 +1,65 @@
+package io.github.wulkanowy.domain.messages
+
+import io.github.wulkanowy.data.db.dao.MailboxDao
+import io.github.wulkanowy.data.db.entities.Mailbox
+import io.github.wulkanowy.data.db.entities.Student
+import javax.inject.Inject
+
+class GetMailboxByStudentUseCase @Inject constructor(
+ private val mailboxDao: MailboxDao,
+) {
+
+ suspend operator fun invoke(student: Student): Mailbox? {
+ return mailboxDao.loadAll(student.email)
+ .filterByStudent(student)
+ }
+
+ private fun List.filterByStudent(student: Student): Mailbox? {
+ val normalizedStudentName = student.studentName.normalizeStudentName()
+
+ return singleOrNull {
+ it.studentName.normalizeStudentName() == normalizedStudentName
+ } ?: singleOrNull {
+ it.studentName.normalizeStudentName() == normalizedStudentName
+ && it.schoolNameShort == student.schoolShortName
+ } ?: singleOrNull {
+ it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart()
+ } ?: singleOrNull {
+ it.studentName.getReversedName() == normalizedStudentName
+ } ?: singleOrNull {
+ it.studentName.getUnauthorizedVersion() == normalizedStudentName
+ }
+ }
+
+ private fun String.normalizeStudentName(): String {
+ return trim().split(" ")
+ .filter { it.isNotBlank() }
+ .joinToString(" ") { part ->
+ part.lowercase().replaceFirstChar { it.uppercase() }
+ }
+ }
+
+ private fun String.getFirstAndLastPart(): String {
+ val parts = normalizeStudentName().split(" ")
+
+ val endParts = parts.filterIndexed { i, _ ->
+ i == 0 || parts.size - 1 == i
+ }
+ return endParts.joinToString(" ")
+ }
+
+ private fun String.getReversedName(): String {
+ val parts = normalizeStudentName().split(" ")
+
+ return parts
+ .asReversed()
+ .joinToString(" ")
+ }
+
+ private fun String.getUnauthorizedVersion(): String {
+ return normalizeStudentName().split(" ")
+ .joinToString(" ") {
+ it.first() + "*".repeat(it.length - 1)
+ }
+ }
+}
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 49842c9a6..99473a8ec 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
@@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Student
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.descriptionRes
import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString
@@ -22,8 +21,9 @@ class NewAttendanceNotification @Inject constructor(
suspend fun notify(items: List, student: Student) {
val lines = items.filterNot { it.presence || it.name == "UNKNOWN" }
.map {
+ val lesson = it.subject.ifBlank { "Lekcja ${it.number}" }
val description = context.getString(it.descriptionRes)
- "${it.date.toFormattedString("dd.MM")} - ${it.subject}: $description"
+ "${it.date.toFormattedString("dd.MM")} - $lesson: $description"
}
.ifEmpty { return }
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 3b7bcff05..45523d51e 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
@@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Student
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 javax.inject.Inject
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 180568267..c7824e61f 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
@@ -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.enums.MessageFolder.RECEIVED
-import io.github.wulkanowy.data.repositories.MailboxRepository
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.NewMessageNotification
@@ -12,12 +11,11 @@ import javax.inject.Inject
class MessageWork @Inject constructor(
private val messageRepository: MessageRepository,
- private val mailboxRepository: MailboxRepository,
private val newMessageNotification: NewMessageNotification,
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
- val mailbox = mailboxRepository.getMailbox(student)
+ val mailbox = messageRepository.getMailboxByStudent(student)
messageRepository.getMessages(
student = student,
mailbox = mailbox,
@@ -26,7 +24,7 @@ class MessageWork @Inject constructor(
notify = notify
).waitForResult()
- messageRepository.getMessagesFromDatabase(mailbox).first()
+ messageRepository.getMessagesFromDatabase(student, mailbox).first()
.filter { !it.isNotified && it.unread }.let {
if (it.isNotEmpty()) newMessageNotification.notify(it, student)
messageRepository.updateMessages(it.onEach { message -> message.isNotified = true })
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 b1322ada3..90b20651d 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
@@ -1,22 +1,23 @@
package io.github.wulkanowy.services.sync.works
+import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.repositories.MailboxRepository
+import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.RecipientRepository
+import io.github.wulkanowy.data.toFirstResult
import javax.inject.Inject
class RecipientWork @Inject constructor(
- private val mailboxRepository: MailboxRepository,
+ private val messageRepository: MessageRepository,
private val recipientRepository: RecipientRepository
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
- mailboxRepository.refreshMailboxes(student)
-
- val mailbox = mailboxRepository.getMailbox(student)
-
- recipientRepository.refreshRecipients(student, mailbox, MailboxType.EMPLOYEE)
+ val mailboxes = messageRepository.getMailboxes(student, forceRefresh = true).toFirstResult()
+ mailboxes.dataOrNull?.forEach {
+ recipientRepository.refreshRecipients(student, it, MailboxType.EMPLOYEE)
+ }
}
}
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 48c003b7e..fe0e64697 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
@@ -4,7 +4,6 @@ import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.os.Bundle
-import android.view.LayoutInflater
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog
@@ -15,6 +14,7 @@ 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.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.DialogErrorBinding
import io.github.wulkanowy.utils.*
import javax.inject.Inject
@@ -25,6 +25,9 @@ class ErrorDialog : DialogFragment() {
@Inject
lateinit var appInfo: AppInfo
+ @Inject
+ lateinit var preferencesRepository: PreferencesRepository
+
companion object {
private const val ARGUMENT_KEY = "error"
@@ -34,9 +37,9 @@ class ErrorDialog : DialogFragment() {
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable
+ val error = requireArguments().serializable(ARGUMENT_KEY)
- val binding = DialogErrorBinding.inflate(LayoutInflater.from(context))
+ val binding = DialogErrorBinding.inflate(layoutInflater)
binding.bindErrorDetails(error)
return getAlertDialog(binding, error).apply {
@@ -99,7 +102,8 @@ class ErrorDialog : DialogFragment() {
R.string.about_feedback_template,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
- "${appInfo.versionName}-${appInfo.buildFlavor}"
+ "${appInfo.versionName}-${appInfo.buildFlavor}",
+ preferencesRepository.installationId,
) + "\n" + content,
onActivityNotFound = {
requireContext().openInternetBrowser(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt
index 2d83bbbf9..e1c234575 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt
@@ -1,6 +1,9 @@
package io.github.wulkanowy.ui.base
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_ACTIVITIES
+import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
@@ -41,9 +44,8 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
)
}
- private fun isThemeApplicable(activity: AppCompatActivity) =
- activity.packageManager
- .getPackageInfo(activity.packageName, GET_ACTIVITIES)
+ private fun isThemeApplicable(activity: AppCompatActivity): Boolean =
+ getPackageInfo(activity)
.activities
.singleOrNull { it.name == activity::class.java.canonicalName }
?.theme
@@ -52,4 +54,14 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
}
+
+ @Suppress("DEPRECATION")
+ private fun getPackageInfo(activity: AppCompatActivity): PackageInfo {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ activity.packageManager.getPackageInfo(
+ activity.packageName,
+ PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong())
+ )
+ } else activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES)
+ }
}
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 701656b55..d7f39e303 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
@@ -6,6 +6,7 @@ import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
+import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentAboutBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment
@@ -30,6 +31,9 @@ class AboutFragment : BaseFragment(R.layout.fragment_about
@Inject
lateinit var appInfo: AppInfo
+ @Inject
+ lateinit var preferencesRepository: PreferencesRepository
+
override val versionRes: Triple?
get() = context?.run {
val buildTimestamp =
@@ -185,7 +189,8 @@ class AboutFragment : BaseFragment(R.layout.fragment_about
R.string.about_feedback_template,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
- "${appInfo.versionName}-${appInfo.buildFlavor}"
+ "${appInfo.versionName}-${appInfo.buildFlavor}",
+ preferencesRepository.installationId,
),
onActivityNotFound = {
requireContext().openInternetBrowser(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt
index 051c93c95..f115372a5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt
@@ -34,6 +34,7 @@ class AccountFragment : BaseFragment(R.layout.fragment_a
override val titleStringId = R.string.account_title
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt
index c3137ec58..41b97b075 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt
@@ -6,6 +6,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AlertDialog
+import androidx.core.os.bundleOf
import androidx.core.view.get
import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
@@ -21,6 +22,7 @@ import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.nickOrName
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -37,12 +39,12 @@ class AccountDetailsFragment :
private const val ARGUMENT_KEY = "Data"
- fun newInstance(student: Student) =
- AccountDetailsFragment().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, student) }
- }
+ fun newInstance(student: Student) = AccountDetailsFragment().apply {
+ arguments = bundleOf(ARGUMENT_KEY to student)
+ }
}
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
@@ -51,7 +53,7 @@ class AccountDetailsFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentAccountDetailsBinding.bind(view)
- presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
+ presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY))
}
override fun initView() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt
index 21a7a492d..6e2bc8c44 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.recyclerview.widget.GridLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.DialogAccountEditBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -24,12 +26,9 @@ class AccountEditDialog : BaseDialogFragment(), Accoun
private const val ARGUMENT_KEY = "student_with_semesters"
- fun newInstance(student: Student) =
- AccountEditDialog().apply {
- arguments = Bundle().apply {
- putSerializable(ARGUMENT_KEY, student)
- }
- }
+ fun newInstance(student: Student) = AccountEditDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to student)
+ }
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -45,7 +44,7 @@ class AccountEditDialog : BaseDialogFragment(), Accoun
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
+ presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY))
}
override fun initView() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt
index 4279102e1..d23978f5f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt
@@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
@@ -13,6 +14,7 @@ import io.github.wulkanowy.ui.modules.account.AccountAdapter
import io.github.wulkanowy.ui.modules.account.AccountFragment
import io.github.wulkanowy.ui.modules.account.AccountItem
import io.github.wulkanowy.ui.modules.main.MainActivity
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -30,9 +32,7 @@ class AccountQuickDialog : BaseDialogFragment(), Acco
fun newInstance(studentsWithSemesters: List) =
AccountQuickDialog().apply {
- arguments = Bundle().apply {
- putSerializable(STUDENTS_ARGUMENT_KEY, studentsWithSemesters.toTypedArray())
- }
+ arguments = bundleOf(STUDENTS_ARGUMENT_KEY to studentsWithSemesters.toTypedArray())
}
}
@@ -49,8 +49,8 @@ class AccountQuickDialog : BaseDialogFragment(), Acco
@Suppress("UNCHECKED_CAST")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- val studentsWithSemesters =
- (requireArguments()[STUDENTS_ARGUMENT_KEY] as Array).toList()
+ val studentsWithSemesters = requireArguments()
+ .serializable>(STUDENTS_ARGUMENT_KEY).toList()
presenter.onAttachView(this, studentsWithSemesters)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
index 9b5c63e4c..eab24f91d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.utils.descriptionRes
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
class AttendanceDialog : DialogFragment() {
@@ -22,16 +24,14 @@ class AttendanceDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Attendance) = AttendanceDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ arguments = bundleOf(ARGUMENT_KEY to exam)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- attendance = getSerializable(ARGUMENT_KEY) as Attendance
- }
+ attendance = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
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 6354b5e04..21f30b046 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
@@ -84,6 +84,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag
}
}
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt
index 477b762b9..7834b6e8b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.databinding.DialogConferenceBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
class ConferenceDialog : DialogFragment() {
@@ -22,16 +24,14 @@ class ConferenceDialog : DialogFragment() {
private const val ARGUMENT_KEY = "item"
fun newInstance(conference: Conference) = ConferenceDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, conference) }
+ arguments = bundleOf(ARGUMENT_KEY to conference)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.let {
- conference = it.getSerializable(ARGUMENT_KEY) as Conference
- }
+ conference = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
@@ -57,4 +57,4 @@ class ConferenceDialog : DialogFragment() {
conferenceDialogAgendaTitle.isVisible = conference.agenda.isNotBlank()
}
}
-}
\ No newline at end of file
+}
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 de0b4a6c9..cd66d6c2f 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
@@ -61,6 +61,7 @@ class DashboardFragment : BaseFragment(R.layout.fragme
fun newInstance() = DashboardFragment()
}
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt
index e220ae236..d019dea68 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt
@@ -33,18 +33,27 @@ sealed class DashboardItem(val type: Type) {
}
data class HorizontalGroup(
- val unreadMessagesCount: Int? = null,
- val attendancePercentage: Double? = null,
- val luckyNumber: Int? = null,
+ val unreadMessagesCount: Cell? = null,
+ val attendancePercentage: Cell? = null,
+ val luckyNumber: Cell? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.HORIZONTAL_GROUP) {
+ data class Cell(
+ val data: T?,
+ val error: Boolean,
+ val isLoading: Boolean,
+ ) {
+ val isHidden: Boolean
+ get() = data == null && !error && !isLoading
+ }
+
override val isDataLoaded
- get() = unreadMessagesCount != null || attendancePercentage != null || luckyNumber != null
+ get() = unreadMessagesCount?.isLoading == false || attendancePercentage?.isLoading == false || luckyNumber?.isLoading == false
val isFullDataLoaded
- get() = luckyNumber != -1 && attendancePercentage != -1.0 && unreadMessagesCount != -1
+ get() = luckyNumber?.isLoading != true && attendancePercentage?.isLoading != true && unreadMessagesCount?.isLoading != true
}
data class Grades(
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 350300937..22b0d267e 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
@@ -25,7 +25,6 @@ class DashboardPresenter @Inject constructor(
private val gradeRepository: GradeRepository,
private val semesterRepository: SemesterRepository,
private val messageRepository: MessageRepository,
- private val mailboxRepository: MailboxRepository,
private val attendanceSummaryRepository: AttendanceSummaryRepository,
private val timetableRepository: TimetableRepository,
private val homeworkRepository: HomeworkRepository,
@@ -227,50 +226,71 @@ class DashboardPresenter @Inject constructor(
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow {
- val semester = semesterRepository.getCurrentSemester(student)
- val mailbox = mailboxRepository.getMailbox(student)
val selectedTiles = preferencesRepository.selectedDashboardTiles
-
val flowSuccess = flowOf(Resource.Success(null))
+
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
.mapResourceData {
it ?: LuckyNumber(0, LocalDate.now(), 0)
}
+ .onResourceError { errorHandler.dispatch(it) }
.takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowSuccess
- val messageFLow = messageRepository.getMessages(
- student = student,
- mailbox = mailbox,
- folder = MessageFolder.RECEIVED,
- forceRefresh = forceRefresh
- ).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
+ val messageFLow = flatResourceFlow {
+ val mailbox = messageRepository.getMailboxByStudent(student)
- val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary(
- student = student,
- semester = semester,
- subjectId = -1,
- forceRefresh = forceRefresh
- ).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess
+ messageRepository.getMessages(
+ student = student,
+ mailbox = mailbox,
+ folder = MessageFolder.RECEIVED,
+ forceRefresh = forceRefresh
+ )
+ }
+ .onResourceError { errorHandler.dispatch(it) }
+ .takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
+
+ val attendanceFlow = flatResourceFlow {
+ val semester = semesterRepository.getCurrentSemester(student)
+ attendanceSummaryRepository.getAttendanceSummary(
+ student = student,
+ semester = semester,
+ subjectId = -1,
+ forceRefresh = forceRefresh
+ )
+ }
+ .onResourceError { errorHandler.dispatch(it) }
+ .takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess
emitAll(
combine(
- luckyNumberFlow,
- messageFLow,
- attendanceFlow
+ flow = luckyNumberFlow,
+ flow2 = messageFLow,
+ flow3 = attendanceFlow,
) { luckyNumberResource, messageResource, attendanceResource ->
val resList = listOf(luckyNumberResource, messageResource, attendanceResource)
- resList.firstNotNullOfOrNull { it.errorOrNull }?.let { throw it }
- val isLoading = resList.any { it is Resource.Loading }
-
- val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber
- val messageCount = messageResource.dataOrNull?.count { it.unread }
- val attendancePercentage = attendanceResource.dataOrNull?.calculatePercentage()
DashboardItem.HorizontalGroup(
- isLoading = isLoading,
- attendancePercentage = if (attendancePercentage == 0.0 && isLoading) -1.0 else attendancePercentage,
- unreadMessagesCount = if (messageCount == 0 && isLoading) -1 else messageCount,
- luckyNumber = if (luckyNumber == 0 && isLoading) -1 else luckyNumber
+ isLoading = resList.any { it is Resource.Loading },
+ error = resList.map { it.errorOrNull }.let { errors ->
+ if (errors.all { it != null }) {
+ errors.firstOrNull()
+ } else null
+ },
+ attendancePercentage = DashboardItem.HorizontalGroup.Cell(
+ data = attendanceResource.dataOrNull?.calculatePercentage(),
+ error = attendanceResource.errorOrNull != null,
+ isLoading = attendanceResource is Resource.Loading,
+ ),
+ unreadMessagesCount = DashboardItem.HorizontalGroup.Cell(
+ data = messageResource.dataOrNull?.count { it.unread },
+ error = messageResource.errorOrNull != null,
+ isLoading = messageResource is Resource.Loading,
+ ),
+ luckyNumber = DashboardItem.HorizontalGroup.Cell(
+ data = luckyNumberResource.dataOrNull?.luckyNumber,
+ error = luckyNumberResource.errorOrNull != null,
+ isLoading = luckyNumberResource is Resource.Loading,
+ )
)
})
}
@@ -281,11 +301,8 @@ class DashboardPresenter @Inject constructor(
if (it.isLoading) {
Timber.i("Loading horizontal group data started")
-
- if (it.isFullDataLoaded) {
- firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP
- }
} else {
+ firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP
Timber.i("Loading horizontal group result: Success")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt
index a3c423a8b..2c06e45fd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt
@@ -171,81 +171,105 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter {
+ updateMarginsRelative(
+ end = if (isAttendanceHidden && isMessagesHidden && !isLuckyNumberHidden) {
+ 0
+ } else context.dpToPx(8f).toInt()
+ )
+ }
+ }
+ }
+
+ private fun ItemDashboardHorizontalGroupBinding.bindMessages(
+ item: DashboardItem.HorizontalGroup,
+ isWideErrorShow: Boolean
+ ) {
+ dashboardHorizontalGroupItemMessageError.isVisible = item.unreadMessagesCount?.error == true
+ with(dashboardHorizontalGroupItemMessageValue) {
+ isVisible = item.unreadMessagesCount?.error != true
+ text = item.unreadMessagesCount?.data.toString()
+ }
+ with(dashboardHorizontalGroupItemMessageContainer) {
+ isVisible = item.unreadMessagesCount?.isHidden == false && !isWideErrorShow
+ setOnClickListener { onMessageTileClickListener() }
+ }
+ }
+
+ private fun ItemDashboardHorizontalGroupBinding.bindAttendance(
+ item: DashboardItem.HorizontalGroup,
+ isWideErrorShow: Boolean
+ ) {
+ val attendancePercentage = item.attendancePercentage?.data
val attendanceColor = when {
attendancePercentage == null || attendancePercentage == .0 -> {
- context.getThemeAttrColor(R.attr.colorOnSurface)
+ root.context.getThemeAttrColor(R.attr.colorOnSurface)
}
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
- context.getThemeAttrColor(R.attr.colorPrimary)
+ root.context.getThemeAttrColor(R.attr.colorPrimary)
}
attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
- context.getThemeAttrColor(R.attr.colorTimetableChange)
+ root.context.getThemeAttrColor(R.attr.colorTimetableChange)
}
- else -> context.getThemeAttrColor(R.attr.colorOnSurface)
+ else -> root.context.getThemeAttrColor(R.attr.colorOnSurface)
}
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
- context.getString(R.string.dashboard_horizontal_group_no_data)
+ root.context.getString(R.string.dashboard_horizontal_group_no_data)
} else {
"%.2f%%".format(attendancePercentage)
}
- with(binding.dashboardHorizontalGroupItemAttendanceValue) {
+ dashboardHorizontalGroupItemAttendanceError.isVisible =
+ item.attendancePercentage?.error == true
+ with(dashboardHorizontalGroupItemAttendanceValue) {
+ isVisible = item.attendancePercentage?.error != true
text = attendanceString
setTextColor(attendanceColor)
}
-
- with(binding) {
- dashboardHorizontalGroupItemMessageValue.text = unreadMessagesCount.toString()
- dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == 0) {
- context.getString(R.string.dashboard_horizontal_group_no_data)
- } else luckyNumber?.toString()
-
- dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoadingVisible
- dashboardHorizontalGroupItemInfoProgress.isVisible = isLoadingVisible
- dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null
-
- with(dashboardHorizontalGroupItemLuckyContainer) {
- isVisible = luckyNumber != null && luckyNumber != -1 && !isLoadingVisible
- setOnClickListener { onLuckyNumberTileClickListener() }
-
- updateLayoutParams {
- updateMarginsRelative(
- end = if (attendancePercentage == null && unreadMessagesCount == null && luckyNumber != null) {
- 0
- } else {
- context.dpToPx(8f).toInt()
- }
- )
+ with(dashboardHorizontalGroupItemAttendanceContainer) {
+ isVisible = item.attendancePercentage?.isHidden == false && !isWideErrorShow
+ setOnClickListener { onAttendanceTileClickListener() }
+ updateLayoutParams {
+ matchConstraintPercentWidth = when {
+ item.luckyNumber?.isHidden == true && item.unreadMessagesCount?.isHidden == true -> 1.0f
+ item.luckyNumber?.isHidden == true || item.unreadMessagesCount?.isHidden == true -> 0.5f
+ else -> 0.4f
}
}
-
- with(dashboardHorizontalGroupItemAttendanceContainer) {
- isVisible =
- attendancePercentage != null && attendancePercentage != -1.0 && !isLoadingVisible
- updateLayoutParams {
- matchConstraintPercentWidth = when {
- luckyNumber == null && unreadMessagesCount == null -> 1.0f
- luckyNumber == null || unreadMessagesCount == null -> 0.5f
- else -> 0.4f
- }
- }
- setOnClickListener { onAttendanceTileClickListener() }
- }
-
- with(dashboardHorizontalGroupItemMessageContainer) {
- isVisible =
- unreadMessagesCount != null && unreadMessagesCount != -1 && !isLoadingVisible
- setOnClickListener { onMessageTileClickListener() }
- }
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt
index 1e11c874b..929e72645 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt
@@ -1,9 +1,7 @@
package io.github.wulkanowy.ui.modules.debug.logviewer
import android.content.Intent
-import android.content.Intent.EXTRA_EMAIL
-import android.content.Intent.EXTRA_STREAM
-import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
+import android.content.Intent.*
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
@@ -36,6 +34,7 @@ class LogViewerFragment : BaseFragment(R.layout.fragme
fun newInstance() = LogViewerFragment()
}
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt
index 6ff26162b..27d8613a3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt
@@ -19,9 +19,12 @@ val debugMessageItems = listOf(
private fun generateMessage(sender: String, subject: String) = Message(
subject = subject,
messageId = 123,
+ email = "",
date = Instant.now(),
folderId = 0,
unread = true,
+ readBy = 2,
+ unreadBy = 2,
hasAttachments = false,
messageGlobalKey = "",
correspondents = sender,
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 41adc008a..876b563f9 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
@@ -4,12 +4,14 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
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
import io.github.wulkanowy.utils.openCalendarEventAdd
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalTime
@@ -24,16 +26,14 @@ class ExamDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Exam) = ExamDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ arguments = bundleOf(ARGUMENT_KEY to exam)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- exam = getSerializable(ARGUMENT_KEY) as Exam
- }
+ exam = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt
index 0a8561eec..15df47a19 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt
@@ -51,6 +51,7 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade
override val currentPageIndex get() = binding.gradeViewPager.currentItem
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
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 34594111f..a1ef2ec5a 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
@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
@@ -27,22 +28,19 @@ class GradeDetailsDialog : DialogFragment() {
private const val COLOR_THEME_KEY = "Theme"
- fun newInstance(grade: Grade, colorTheme: GradeColorTheme) =
- GradeDetailsDialog().apply {
- arguments = Bundle().apply {
- putSerializable(ARGUMENT_KEY, grade)
- putSerializable(COLOR_THEME_KEY, colorTheme)
- }
- }
+ fun newInstance(grade: Grade, colorTheme: GradeColorTheme) = GradeDetailsDialog().apply {
+ arguments = bundleOf(
+ ARGUMENT_KEY to grade,
+ COLOR_THEME_KEY to colorTheme
+ )
+ }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- grade = getSerializable(ARGUMENT_KEY) as Grade
- gradeColorTheme = getSerializable(COLOR_THEME_KEY) as GradeColorTheme
- }
+ grade = requireArguments().serializable(ARGUMENT_KEY)
+ gradeColorTheme = requireArguments().serializable(COLOR_THEME_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt
index 81f3226ad..23d767a6f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt
@@ -5,9 +5,7 @@ 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 androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
@@ -42,6 +40,7 @@ class GradeDetailsFragment :
override val isViewEmpty
get() = gradeDetailsAdapter.itemCount == 0
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
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 8cde5d6b9..4261c507d 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
@@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.grade.details
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
import io.github.wulkanowy.data.enums.GradeSortingMode.*
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
@@ -132,16 +131,17 @@ class GradeDetailsPresenter @Inject constructor(
}
.logResourceStatus("load grade details")
.onResourceData {
+ val gradeItems = createGradeItems(it)
view?.run {
enableSwipe(true)
showProgress(false)
showErrorView(false)
- showContent(it.isNotEmpty())
- showEmpty(it.isEmpty())
+ showContent(gradeItems.isNotEmpty())
+ showEmpty(gradeItems.isEmpty())
updateNewGradesAmount(it)
updateMarkAsDoneButton()
updateData(
- data = createGradeItems(it),
+ data = gradeItems,
expandMode = preferencesRepository.gradeExpandMode,
preferencesRepository.gradeColorTheme
)
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 2af59c011..edc384c5e 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
@@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.setOnItemSelectedListener
import javax.inject.Inject
@@ -48,8 +49,8 @@ class GradeStatisticsFragment :
messageContainer = binding.gradeStatisticsRecycler
presenter.onAttachView(
view = this,
- type = savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType,
- subjectName = savedInstanceState?.getSerializable(SAVED_SUBJECT_NAME) as? String,
+ type = savedInstanceState?.serializable(SAVED_CHART_TYPE),
+ subjectName = savedInstanceState?.serializable(SAVED_SUBJECT_NAME),
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt
index 082c847e5..8dcade56e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt
@@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
+import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
import io.github.wulkanowy.utils.calcFinalAverage
import java.util.Locale
import javax.inject.Inject
@@ -61,7 +62,7 @@ class GradeSummaryAdapter @Inject constructor(
if (items.isEmpty()) return
val context = binding.root.context
- val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) }
+ val finalItemsCount = items.count { isGradeValid(it.finalGrade) }
val calculatedItemsCount = items.count { value -> value.average != 0.0 }
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
val finalAverage = items.calcFinalAverage(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt
index f9d463510..5e2cc65dc 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt
@@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
@@ -14,6 +15,7 @@ import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.databinding.DialogHomeworkBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.openInternetBrowser
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -35,16 +37,14 @@ class HomeworkDetailsDialog : BaseDialogFragment(), Homew
private const val ARGUMENT_KEY = "Item"
fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, homework) }
+ arguments = bundleOf(ARGUMENT_KEY to homework)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- homework = getSerializable(ARGUMENT_KEY) as Homework
- }
+ homework = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
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 aac60b56d..c17c92efd 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
@@ -2,13 +2,17 @@ package io.github.wulkanowy.ui.modules.login
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Bundle
import android.view.MenuItem
+import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE
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.data.pojos.RegisterUser
import io.github.wulkanowy.databinding.ActivityLoginBinding
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment
@@ -16,6 +20,9 @@ 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.ui.modules.main.MainActivity
+import io.github.wulkanowy.ui.modules.notifications.NotificationsFragment
+import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.UpdateHelper
import javax.inject.Inject
@@ -28,6 +35,9 @@ class LoginActivity : BaseActivity(), Logi
@Inject
lateinit var updateHelper: UpdateHelper
+ @Inject
+ lateinit var appInfo: AppInfo
+
companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
}
@@ -55,7 +65,7 @@ class LoginActivity : BaseActivity(), Logi
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == android.R.id.home) onBackPressed()
+ if (item.itemId == android.R.id.home) onBackPressedDispatcher.onBackPressed()
return true
}
@@ -67,8 +77,24 @@ class LoginActivity : BaseActivity(), Logi
openFragment(LoginSymbolFragment.newInstance(loginData))
}
- fun navigateToStudentSelect(studentsWithSemesters: List) {
- openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters))
+ fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
+ openFragment(LoginStudentSelectFragment.newInstance(loginData, registerUser))
+ }
+
+ fun navigateToNotifications() {
+ val isNotificationsPermissionRequired = appInfo.systemVersion >= TIRAMISU
+ val isPermissionGranted = ContextCompat.checkSelfPermission(
+ this, "android.permission.POST_NOTIFICATIONS"
+ ) == PackageManager.PERMISSION_GRANTED
+
+ if (isNotificationsPermissionRequired && !isPermissionGranted) {
+ openFragment(NotificationsFragment.newInstance(), clearBackStack = true)
+ } else navigateToFinish()
+ }
+
+ fun navigateToFinish() {
+ startActivity(MainActivity.getStartIntent(this))
+ finish()
}
fun onAdvancedLoginClick() {
@@ -80,6 +106,8 @@ class LoginActivity : BaseActivity(), Logi
}
private fun openFragment(fragment: Fragment, clearBackStack: Boolean = false) {
+ supportFragmentManager.popBackStack(fragment::class.java.name, POP_BACK_STACK_INCLUSIVE)
+
supportFragmentManager.commit {
replace(R.id.loginContainer, fragment)
setReorderingAllowed(true)
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
index 5d4743589..ae6c22492 100644
--- 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
@@ -6,4 +6,5 @@ data class LoginData(
val login: String,
val password: String,
val baseUrl: String,
+ val symbol: String?,
) : Serializable
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 37dcb38b3..8c90623e1 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
@@ -8,7 +8,7 @@ import android.widget.ArrayAdapter
import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.databinding.FragmentLoginAdvancedBinding
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.base.BaseFragment
@@ -327,8 +327,8 @@ class LoginAdvancedFragment :
(activity as? LoginActivity)?.navigateToSymbolFragment(loginData)
}
- override fun navigateToStudentSelect(studentsWithSemesters: List) {
- (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
+ override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
+ (activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser)
}
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 1b42c6c52..33a76e5f9 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
@@ -4,9 +4,15 @@ 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.pojos.RegisterStudent
+import io.github.wulkanowy.data.pojos.RegisterSymbol
+import io.github.wulkanowy.data.pojos.RegisterUnit
+import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.sdk.Sdk
+import io.github.wulkanowy.sdk.scrapper.Scrapper
+import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
@@ -142,19 +148,23 @@ class LoginAdvancedPresenter @Inject constructor(
is Resource.Success -> {
analytics.logEvent(
"registration_form",
- "success" to true,
- "students" to it.data.size,
- "error" to "No error"
- )
- 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)
- }
+ "success" to true,
+ "students" to it.data.size,
+ "error" to "No error"
+ )
+ val loginData = LoginData(
+ login = view?.formUsernameValue.orEmpty().trim(),
+ password = view?.formPassValue.orEmpty().trim(),
+ baseUrl = view?.formHostValue.orEmpty().trim(),
+ symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
+ )
+ when (it.data.size) {
+ 0 -> view?.navigateToSymbol(loginData)
+ else -> view?.navigateToStudentSelect(
+ loginData = loginData,
+ registerUser = it.data.toRegisterUser(loginData),
+ )
+ }
}
is Resource.Error -> {
analytics.logEvent(
@@ -173,6 +183,58 @@ class LoginAdvancedPresenter @Inject constructor(
}.launch("login")
}
+ private fun List.toRegisterUser(loginData: LoginData) = RegisterUser(
+ email = loginData.login,
+ password = loginData.password,
+ login = loginData.login,
+ baseUrl = loginData.baseUrl,
+ loginType = firstOrNull()?.student?.loginType?.let(
+ Scrapper.LoginType::valueOf
+ ) ?: Scrapper.LoginType.AUTO,
+ symbols = this
+ .groupBy { students -> students.student.symbol }
+ .map { (symbol, students) ->
+ RegisterSymbol(
+ symbol = symbol,
+ error = null,
+ userName = "",
+ schools = students
+ .groupBy { student ->
+ Triple(
+ first = student.student.schoolSymbol,
+ second = student.student.userLoginId,
+ third = student.student.schoolShortName
+ )
+ }
+ .map { (groupKey, students) ->
+ val (schoolId, loginId, schoolName) = groupKey
+ RegisterUnit(
+ students = students.map {
+ RegisterStudent(
+ studentId = it.student.studentId,
+ studentName = it.student.studentName,
+ studentSecondName = it.student.studentName,
+ studentSurname = it.student.studentName,
+ className = it.student.className,
+ classId = it.student.classId,
+ isParent = it.student.isParent,
+ semesters = it.semesters,
+ )
+ },
+ userLoginId = loginId,
+ schoolId = schoolId,
+ schoolName = schoolName,
+ schoolShortName = schoolName,
+ parentIds = listOf(),
+ studentIds = listOf(),
+ employeeIds = listOf(),
+ error = null
+ )
+ }
+ )
+ },
+ )
+
private suspend fun getStudentsAppropriatesToLoginType(): List {
val email = view?.formUsernameValue.orEmpty()
val password = view?.formPassValue.orEmpty()
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 f9b84f1ab..824fa0288 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
@@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules.login.advanced
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData
@@ -72,7 +73,7 @@ interface LoginAdvancedView : BaseView {
fun navigateToSymbol(loginData: LoginData)
- fun navigateToStudentSelect(studentsWithSemesters: List)
+ fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser)
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 d31f5cf0f..a0e7608d6 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
@@ -9,7 +9,8 @@ import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.pojos.RegisterUser
+import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginFormBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
@@ -32,6 +33,9 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
@Inject
lateinit var appInfo: AppInfo
+ @Inject
+ lateinit var preferencesRepository: PreferencesRepository
+
companion object {
fun newInstance() = LoginFormFragment()
}
@@ -222,8 +226,8 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
(activity as? LoginActivity)?.navigateToSymbolFragment(loginData)
}
- override fun navigateToStudentSelect(studentsWithSemesters: List) {
- (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
+ override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
+ (activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser)
}
override fun openAdvancedLogin() {
@@ -260,8 +264,9 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
R.string.login_email_text,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
- appInfo.versionName,
+ "${appInfo.versionName}-${appInfo.buildFlavor}",
"$formHostValue/$formHostSymbol",
+ preferencesRepository.installationId,
lastError
)
)
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 0acb0ea6d..8035ea0ad 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
@@ -93,7 +93,7 @@ class LoginFormPresenter @Inject constructor(
if (!validateCredentials(email, password, host)) return
resourceFlow {
- studentRepository.getStudentsScrapper(
+ studentRepository.getUserSubjectsFromScrapper(
email = email,
password = password,
scrapperBaseUrl = host,
@@ -109,14 +109,14 @@ class LoginFormPresenter @Inject constructor(
}
}
.onResourceSuccess {
- when (it.size) {
- 0 -> view?.navigateToSymbol(LoginData(email, password, host))
- else -> view?.navigateToStudentSelect(it)
+ val loginData = LoginData(email, password, host, symbol)
+ when (it.symbols.size) {
+ 0 -> view?.navigateToSymbol(loginData)
+ else -> view?.navigateToStudentSelect(loginData, it)
}
analytics.logEvent(
"registration_form",
"success" to true,
- "students" to it.size,
"scrapperBaseUrl" to host,
"error" to "No error"
)
@@ -134,7 +134,6 @@ class LoginFormPresenter @Inject constructor(
analytics.logEvent(
"registration_form",
"success" to false,
- "students" to -1,
"scrapperBaseUrl" to host,
"error" to it.message.ifNullOrBlank { "No message" }
)
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 8003975db..5a816fb32 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
@@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.login.form
-import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData
@@ -60,7 +60,7 @@ interface LoginFormView : BaseView {
fun navigateToSymbol(loginData: LoginData)
- fun navigateToStudentSelect(studentsWithSemesters: List)
+ fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser)
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 786bbfce8..b9afba986 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
@@ -98,7 +98,7 @@ class LoginRecoverFragment :
loginRecoverButton.setOnClickListener { presenter.onRecoverClick() }
loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() }
loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() }
- loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressed() }
+ loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressedDispatcher.onBackPressed() }
}
with(bindingLocal.loginRecoverHost) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt
index c046c2ff5..e6d131829 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt
@@ -2,65 +2,182 @@ package io.github.wulkanowy.ui.modules.login.studentselect
import android.annotation.SuppressLint
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.DiffUtil.ItemCallback
+import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
-import io.github.wulkanowy.data.db.entities.StudentWithSemesters
-import io.github.wulkanowy.databinding.ItemLoginStudentSelectBinding
+import io.github.wulkanowy.R
+import io.github.wulkanowy.databinding.*
import javax.inject.Inject
+@SuppressLint("SetTextI18n")
class LoginStudentSelectAdapter @Inject constructor() :
- RecyclerView.Adapter() {
+ ListAdapter(Differ) {
- private val checkedList = mutableMapOf()
+ override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal
- var items = emptyList>()
- set(value) {
- field = value
- checkedList.clear()
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ return when (LoginStudentSelectItemType.values()[viewType]) {
+ LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder(
+ ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false),
+ )
+ LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder(
+ ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false)
+ )
+ LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder(
+ ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false)
+ )
+ LoginStudentSelectItemType.STUDENT -> StudentViewHolder(
+ ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false)
+ )
+ LoginStudentSelectItemType.HELP -> HelpViewHolder(
+ ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false)
+ )
}
+ }
- var onClickListener: (StudentWithSemesters, alreadySaved: Boolean) -> Unit = { _, _ -> }
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ when (holder) {
+ is EmptySymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.EmptySymbolsHeader)
+ is SymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SymbolHeader)
+ is SchoolHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SchoolHeader)
+ is StudentViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Student)
+ is HelpViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Help)
+ }
+ }
- override fun getItemCount() = items.size
+ private class EmptySymbolsHeaderViewHolder(
+ private val binding: ItemLoginStudentSelectEmptySymbolHeaderBinding,
+ ) : RecyclerView.ViewHolder(binding.root) {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
- ItemLoginStudentSelectBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- )
-
- @SuppressLint("SetTextI18n")
- override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
- val (studentAndSemesters, alreadySaved) = items[position]
- val student = studentAndSemesters.student
- val semesters = studentAndSemesters.semesters
- val diary = semesters.maxByOrNull { it.semesterId }
-
- with(holder.binding) {
- loginItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}"
- loginItemSchool.text = student.schoolName
- loginItemName.isEnabled = !alreadySaved
- loginItemSchool.isEnabled = !alreadySaved
- loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE
-
- with(loginItemCheck) {
- isEnabled = !alreadySaved
- keyListener = null
- isChecked = checkedList[position] ?: false
+ fun bind(item: LoginStudentSelectItem.EmptySymbolsHeader) {
+ with(binding) {
+ loginStudentSelectEmptySymbolChevron.rotation = if (item.isExpanded) 270f else 90f
+ root.setOnClickListener { item.onClick() }
}
+ }
+ }
- root.setOnClickListener {
- onClickListener(studentAndSemesters, alreadySaved)
+ private class SymbolsHeaderViewHolder(
+ private val binding: ItemLoginStudentSelectHeaderSymbolBinding,
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(item: LoginStudentSelectItem.SymbolHeader) {
+ with(binding) {
+ loginStudentSelectHeaderSymbolValue.text = buildString {
+ append(root.context.getString(R.string.mobile_device_symbol))
+ append(": ")
+ append(item.humanReadableName ?: item.symbol.symbol)
+ if (!item.humanReadableName.isNullOrBlank()) {
+ append(" (${item.symbol.symbol})")
+ }
+ }
+ loginStudentSelectHeaderSymbolUsername.text = item.symbol.userName
+ loginStudentSelectHeaderSymbolUsername.isVisible = item.symbol.userName.isNotBlank()
+ loginStudentSelectHeaderSymbolError.text = item.symbol.error?.message
+ loginStudentSelectHeaderSymbolError.isVisible = item.symbol.error != null
+ loginStudentSelectHeaderSymbolError.maxLines = when {
+ item.isErrorExpanded -> Int.MAX_VALUE
+ else -> 2
+ }
+
+ if (item.symbol.error != null) {
+ root.setOnClickListener { item.onClick(item.symbol) }
+ } else root.setOnClickListener(null)
+ }
+ }
+ }
+
+ private class SchoolHeaderViewHolder(
+ private val binding: ItemLoginStudentSelectHeaderSchoolBinding,
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(item: LoginStudentSelectItem.SchoolHeader) {
+ with(binding) {
+ loginStudentSelectHeaderSchoolName.text = buildString {
+ append(item.unit.schoolName.trim())
+ append(" (")
+ append(item.unit.schoolShortName)
+ append(")")
+ }
+ loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty()
+ loginStudentSelectHeaderSchoolError.text = item.unit.error?.message
+ loginStudentSelectHeaderSchoolError.isVisible = item.unit.error != null
+ loginStudentSelectHeaderSchoolError.maxLines = when {
+ item.isErrorExpanded -> Int.MAX_VALUE
+ else -> 2
+ }
+
+ if (item.unit.error != null) {
+ root.setOnClickListener { item.onClick(item.unit) }
+ } else root.setOnClickListener(null)
+ }
+ }
+ }
+
+ private class StudentViewHolder(
+ private val binding: ItemLoginStudentSelectStudentBinding,
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(item: LoginStudentSelectItem.Student) {
+ val student = item.student
+ val semesters = student.semesters
+ val diary = semesters.maxByOrNull { it.semesterId }
+
+ with(binding) {
+ loginItemName.text = "${student.studentName} ${student.studentSurname}"
+ loginItemName.isEnabled = item.isEnabled
+ loginItemSignedIn.text = if (!item.isEnabled) {
+ root.context.getString(R.string.login_signed_in)
+ } else diary?.diaryName
with(loginItemCheck) {
- if (isEnabled) {
- isChecked = !isChecked
- checkedList[position] = isChecked
- }
+ keyListener = null
+ isEnabled = item.isEnabled
+ isChecked = item.isSelected || !item.isEnabled
+ }
+
+ root.isEnabled = item.isEnabled
+ root.setOnClickListener {
+ item.onClick(item)
}
}
}
}
- class ItemViewHolder(val binding: ItemLoginStudentSelectBinding) :
- RecyclerView.ViewHolder(binding.root)
+ private class HelpViewHolder(
+ private val binding: ItemLoginStudentSelectHelpBinding,
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(item: LoginStudentSelectItem.Help) {
+ with(binding) {
+ loginStudentSelectHelpSymbol.isVisible = item.isSymbolButtonVisible
+ loginStudentSelectHelpSymbol.setOnClickListener { item.onEnterSymbolClick() }
+ loginStudentSelectHelpMail.setOnClickListener { item.onContactUsClick() }
+ loginStudentSelectHelpDiscord.setOnClickListener { item.onDiscordClick() }
+ }
+ }
+ }
+
+ private object Differ : ItemCallback() {
+
+ override fun areItemsTheSame(
+ oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem
+ ): Boolean = when {
+ oldItem is LoginStudentSelectItem.EmptySymbolsHeader && newItem is LoginStudentSelectItem.EmptySymbolsHeader -> true
+ oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> {
+ oldItem.symbol == newItem.symbol
+ }
+ oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> {
+ oldItem.student == newItem.student
+ }
+ else -> oldItem == newItem
+ }
+
+ override fun areContentsTheSame(
+ oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem
+ ): Boolean = oldItem == newItem
+ }
}
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 6c910fe03..169702151 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
@@ -2,20 +2,20 @@ package io.github.wulkanowy.ui.modules.login.studentselect
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 androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.pojos.RegisterUser
+import io.github.wulkanowy.data.repositories.PreferencesRepository
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.ui.modules.login.LoginData
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -32,12 +32,26 @@ class LoginStudentSelectFragment :
@Inject
lateinit var appInfo: AppInfo
- companion object {
- const val ARG_STUDENTS = "STUDENTS"
+ @Inject
+ lateinit var preferencesRepository: PreferencesRepository
- fun newInstance(studentsWithSemesters: List) =
+ private lateinit var symbolsNames: Array
+ private lateinit var symbolsValues: Array
+
+ override val symbols: Map by lazy {
+ symbolsValues.zip(symbolsNames).toMap()
+ }
+
+ companion object {
+ private const val ARG_LOGIN = "LOGIN"
+ private const val ARG_STUDENTS = "STUDENTS"
+
+ fun newInstance(loginData: LoginData, registerUser: RegisterUser) =
LoginStudentSelectFragment().apply {
- arguments = bundleOf(ARG_STUDENTS to studentsWithSemesters)
+ arguments = bundleOf(
+ ARG_LOGIN to loginData,
+ ARG_STUDENTS to registerUser,
+ )
}
}
@@ -45,62 +59,50 @@ class LoginStudentSelectFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentLoginStudentSelectBinding.bind(view)
+
+ symbolsNames = resources.getStringArray(R.array.symbols)
+ symbolsValues = resources.getStringArray(R.array.symbols_values)
+
presenter.onAttachView(
view = this,
- students = requireArguments().getSerializable(ARG_STUDENTS) as List,
+ loginData = requireArguments().serializable(ARG_LOGIN),
+ registerUser = requireArguments().serializable(ARG_STUDENTS),
)
}
override fun initView() {
(requireActivity() as LoginActivity).showActionBar(true)
- loginAdapter.onClickListener = presenter::onItemSelected
-
with(binding) {
loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() }
- loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() }
- loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() }
-
- with(loginStudentSelectRecycler) {
- layoutManager = LinearLayoutManager(context)
- adapter = loginAdapter
- }
+ loginStudentSelectRecycler.adapter = loginAdapter
}
}
- override fun updateData(data: List>) {
- with(loginAdapter) {
- items = data
- notifyDataSetChanged()
- }
+ override fun updateData(data: List) {
+ loginAdapter.submitList(data)
}
- override fun openMainView() {
- startActivity(MainActivity.getStartIntent(requireContext()))
- requireActivity().finish()
+ override fun navigateToSymbol(loginData: LoginData) {
+ (requireActivity() as LoginActivity).navigateToSymbolFragment(loginData)
+ }
+
+ override fun navigateToNext() {
+ (requireActivity() as LoginActivity).navigateToNotifications()
}
override fun showProgress(show: Boolean) {
- binding.loginStudentSelectProgress.visibility = if (show) VISIBLE else GONE
+ binding.loginStudentSelectProgress.isVisible = show
}
override fun showContent(show: Boolean) {
- binding.loginStudentSelectContent.visibility = if (show) VISIBLE else GONE
+ binding.loginStudentSelectContent.isVisible = show
}
override fun enableSignIn(enable: Boolean) {
binding.loginStudentSelectSignIn.isEnabled = enable
}
- override fun showContact(show: Boolean) {
- binding.loginStudentSelectContact.visibility = if (show) VISIBLE else GONE
- }
-
- override fun onDestroyView() {
- presenter.onDetachView()
- super.onDestroyView()
- }
-
override fun openDiscordInvite() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
}
@@ -111,12 +113,19 @@ class LoginStudentSelectFragment :
email = "wulkanowyinc@gmail.com",
subject = requireContext().getString(R.string.login_email_subject),
body = requireContext().getString(
- R.string.login_email_text, appInfo.systemModel,
+ R.string.login_email_text,
+ "${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
- appInfo.versionName,
+ "${appInfo.versionName}-${appInfo.buildFlavor}",
"Select users to log in",
+ preferencesRepository.installationId,
lastError
)
)
}
+
+ override fun onDestroyView() {
+ presenter.onDetachView()
+ super.onDestroyView()
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt
new file mode 100644
index 000000000..1edc8e7b4
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt
@@ -0,0 +1,50 @@
+package io.github.wulkanowy.ui.modules.login.studentselect
+
+import io.github.wulkanowy.data.pojos.RegisterStudent
+import io.github.wulkanowy.data.pojos.RegisterSymbol
+import io.github.wulkanowy.data.pojos.RegisterUnit
+
+sealed class LoginStudentSelectItem(val type: LoginStudentSelectItemType) {
+
+ data class EmptySymbolsHeader(
+ val isExpanded: Boolean,
+ val onClick: () -> Unit,
+ ) : LoginStudentSelectItem(LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER)
+
+ data class SymbolHeader(
+ val symbol: RegisterSymbol,
+ val humanReadableName: String?,
+ val isErrorExpanded: Boolean,
+ val onClick: (RegisterSymbol) -> Unit,
+ ) : LoginStudentSelectItem(LoginStudentSelectItemType.SYMBOL_HEADER)
+
+ data class SchoolHeader(
+ val unit: RegisterUnit,
+ val isErrorExpanded: Boolean,
+ val onClick: (RegisterUnit) -> Unit,
+ ) : LoginStudentSelectItem(LoginStudentSelectItemType.SCHOOL_HEADER)
+
+ data class Student(
+ val symbol: RegisterSymbol,
+ val unit: RegisterUnit,
+ val student: RegisterStudent,
+ val isEnabled: Boolean,
+ val isSelected: Boolean,
+ val onClick: (Student) -> Unit,
+ ) : LoginStudentSelectItem(LoginStudentSelectItemType.STUDENT)
+
+ data class Help(
+ val onEnterSymbolClick: () -> Unit,
+ val onContactUsClick: () -> Unit,
+ val onDiscordClick: () -> Unit,
+ val isSymbolButtonVisible: Boolean,
+ ) : LoginStudentSelectItem(LoginStudentSelectItemType.HELP)
+}
+
+enum class LoginStudentSelectItemType {
+ EMPTY_SYMBOLS_HEADER,
+ SYMBOL_HEADER,
+ SCHOOL_HEADER,
+ STUDENT,
+ HELP,
+}
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 3455b3cf1..7e1fe3b21 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,15 +1,24 @@
package io.github.wulkanowy.ui.modules.login.studentselect
import io.github.wulkanowy.data.Resource
-import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.logResourceStatus
+import io.github.wulkanowy.data.mappers.mapToStudentWithSemesters
+import io.github.wulkanowy.data.pojos.RegisterStudent
+import io.github.wulkanowy.data.pojos.RegisterSymbol
+import io.github.wulkanowy.data.pojos.RegisterUnit
+import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
+import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException
+import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
import io.github.wulkanowy.services.sync.SyncManager
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.AppInfo
import io.github.wulkanowy.utils.ifNullOrBlank
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
@@ -19,18 +28,30 @@ class LoginStudentSelectPresenter @Inject constructor(
studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler,
private val syncManager: SyncManager,
- private val analytics: AnalyticsHelper
+ private val analytics: AnalyticsHelper,
+ private val appInfo: AppInfo,
) : BasePresenter(loginErrorHandler, studentRepository) {
private var lastError: Throwable? = null
- private val selectedStudents = mutableListOf()
+ private lateinit var registerUser: RegisterUser
+ private lateinit var loginData: LoginData
- fun onAttachView(view: LoginStudentSelectView, students: List) {
+ private lateinit var students: List
+ private var isEmptySymbolsExpanded = false
+ private var expandedSymbolError: RegisterSymbol? = null
+ private var expandedSchoolError: RegisterUnit? = null
+
+ private val selectedStudents = mutableListOf()
+
+ fun onAttachView(
+ view: LoginStudentSelectView,
+ loginData: LoginData,
+ registerUser: RegisterUser,
+ ) {
super.onAttachView(view)
with(view) {
initView()
- showContact(false)
enableSignIn(false)
loginErrorHandler.onStudentDuplicate = {
showMessage(it)
@@ -38,50 +59,171 @@ class LoginStudentSelectPresenter @Inject constructor(
}
}
- if (students.size == 1) registerStudents(students)
- loadData(students)
+ this.loginData = loginData
+ this.registerUser = registerUser
+ loadData()
}
+ private fun loadData() {
+ resetSelectedState()
+
+ resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
+ students = it.dataOrNull.orEmpty()
+ when (it) {
+ is Resource.Loading -> Timber.d("Login student select students load started")
+ is Resource.Success -> refreshItems()
+ is Resource.Error -> {
+ errorHandler.dispatch(it.error)
+ lastError = it.error
+ refreshItems()
+ }
+ }
+ }.launch()
+ }
+
+ private fun createItems(): List = buildList {
+ val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
+ val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
+
+ if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.symbol }) {
+ add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.symbol }))
+ }
+
+ addAll(createNotEmptySymbolItems(notEmptySymbols, students))
+ addAll(createEmptySymbolItems(emptySymbols, notEmptySymbols.isNotEmpty()))
+
+ val helpItem = LoginStudentSelectItem.Help(
+ onEnterSymbolClick = ::onEnterSymbol,
+ onContactUsClick = ::onEmailClick,
+ onDiscordClick = ::onDiscordClick,
+ isSymbolButtonVisible = "login" !in loginData.baseUrl,
+ )
+ add(helpItem)
+ }
+
+ private fun createNotEmptySymbolItems(
+ notEmptySymbols: List,
+ students: List,
+ ) = buildList {
+ notEmptySymbols.forEach { registerSymbol ->
+ val symbolHeader = LoginStudentSelectItem.SymbolHeader(
+ symbol = registerSymbol,
+ humanReadableName = view?.symbols?.get(registerSymbol.symbol),
+ isErrorExpanded = expandedSymbolError == registerSymbol,
+ onClick = ::onSymbolItemClick,
+ )
+ add(symbolHeader)
+
+ registerSymbol.schools.forEach { registerUnit ->
+ val schoolHeader = LoginStudentSelectItem.SchoolHeader(
+ unit = registerUnit,
+ isErrorExpanded = expandedSchoolError == registerUnit,
+ onClick = ::onUnitItemClick,
+ )
+ add(schoolHeader)
+
+ registerUnit.students.forEach {
+ add(createStudentItem(it, registerSymbol, registerUnit, students))
+ }
+ }
+ }
+ }
+
+ private fun createStudentItem(
+ student: RegisterStudent,
+ symbol: RegisterSymbol,
+ school: RegisterUnit,
+ students: List,
+ ) = LoginStudentSelectItem.Student(
+ symbol = symbol,
+ unit = school,
+ student = student,
+ onClick = ::onItemSelected,
+ isEnabled = students.none {
+ it.student.email == registerUser.login
+ && it.student.symbol == symbol.symbol
+ && it.student.studentId == student.studentId
+ && it.student.schoolSymbol == school.schoolId
+ && it.student.classId == student.classId
+ },
+ isSelected = selectedStudents
+ .filter { it.symbol.symbol == symbol.symbol }
+ .filter { it.unit.schoolId == school.schoolId }
+ .filter { it.student.studentId == student.studentId }
+ .filter { it.student.classId == student.classId }
+ .size == 1,
+ )
+
+ private fun createEmptySymbolItems(
+ emptySymbols: List,
+ isNotEmptySymbolsExist: Boolean,
+ ) = buildList {
+ val filteredEmptySymbols = emptySymbols.filter {
+ it.error !is InvalidSymbolException
+ }.ifEmpty { emptySymbols.takeIf { !isNotEmptySymbolsExist }.orEmpty() }
+
+ if (filteredEmptySymbols.isNotEmpty() && isNotEmptySymbolsExist) {
+ val emptyHeader = LoginStudentSelectItem.EmptySymbolsHeader(
+ isExpanded = isEmptySymbolsExpanded,
+ onClick = ::onEmptySymbolsToggle,
+ )
+ add(emptyHeader)
+ if (isEmptySymbolsExpanded) {
+ filteredEmptySymbols.forEach {
+ add(createEmptySymbolItem(it))
+ }
+ }
+ }
+
+ if (filteredEmptySymbols.isNotEmpty() && !isNotEmptySymbolsExist) {
+ filteredEmptySymbols.forEach {
+ add(createEmptySymbolItem(it))
+ }
+ }
+ }
+
+ private fun createEmptySymbolItem(registerSymbol: RegisterSymbol) =
+ LoginStudentSelectItem.SymbolHeader(
+ symbol = registerSymbol,
+ humanReadableName = view?.symbols?.get(registerSymbol.symbol),
+ isErrorExpanded = expandedSymbolError == registerSymbol,
+ onClick = ::onSymbolItemClick,
+ )
+
fun onSignIn() {
registerStudents(selectedStudents)
}
- fun onItemSelected(studentWithSemester: StudentWithSemesters, alreadySaved: Boolean) {
- if (alreadySaved) return
+ private fun onEmptySymbolsToggle() {
+ isEmptySymbolsExpanded = !isEmptySymbolsExpanded
+
+ refreshItems()
+ }
+
+ private fun onItemSelected(item: LoginStudentSelectItem.Student) {
+ if (!item.isEnabled) return
selectedStudents
- .removeAll { it == studentWithSemester }
- .let { if (!it) selectedStudents.add(studentWithSemester) }
+ .removeAll {
+ it.student.studentId == item.student.studentId &&
+ it.student.classId == item.student.classId &&
+ it.unit.schoolId == item.unit.schoolId &&
+ it.symbol.symbol == item.symbol.symbol
+ }
+ .let { if (!it) selectedStudents.add(item) }
view?.enableSignIn(selectedStudents.isNotEmpty())
+ refreshItems()
}
- private fun compareStudents(a: Student, b: Student): Boolean {
- return a.email == b.email
- && a.symbol == b.symbol
- && a.studentId == b.studentId
- && a.schoolSymbol == b.schoolSymbol
- && a.classId == b.classId
+ private fun onSymbolItemClick(symbol: RegisterSymbol) {
+ expandedSymbolError = if (symbol != expandedSymbolError) symbol else null
+ refreshItems()
}
- private fun loadData(studentsWithSemesters: List) {
- resetSelectedState()
-
- 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)
- }
- })
- is Resource.Error -> {
- errorHandler.dispatch(it.error)
- lastError = it.error
- view?.updateData(studentsWithSemesters.map { student -> student to false })
- }
- }
- }.launch()
+ private fun onUnitItemClick(unit: RegisterUnit) {
+ expandedSchoolError = if (unit != expandedSchoolError) unit else null
+ refreshItems()
}
private fun resetSelectedState() {
@@ -89,7 +231,20 @@ class LoginStudentSelectPresenter @Inject constructor(
view?.enableSignIn(false)
}
- private fun registerStudents(studentsWithSemesters: List) {
+ private fun refreshItems() {
+ view?.updateData(createItems())
+ }
+
+ private fun registerStudents(students: List) {
+ val studentsWithSemesters = students
+ .filterIsInstance().map { item ->
+ item.student.mapToStudentWithSemesters(
+ user = registerUser,
+ symbol = item.symbol,
+ unit = item.unit,
+ colors = appInfo.defaultColorsForAvatar,
+ )
+ }
resourceFlow { studentRepository.saveStudents(studentsWithSemesters) }
.logResourceStatus("registration")
.onEach {
@@ -100,14 +255,13 @@ class LoginStudentSelectPresenter @Inject constructor(
}
is Resource.Success -> {
syncManager.startOneTimeSyncWorker(quiet = true)
- view?.openMainView()
+ view?.navigateToNext()
logRegisterEvent(studentsWithSemesters)
}
is Resource.Error -> {
view?.apply {
showProgress(false)
showContent(true)
- showContact(true)
}
lastError = it.error
loginErrorHandler.dispatch(it.error)
@@ -117,12 +271,37 @@ class LoginStudentSelectPresenter @Inject constructor(
}.launch("register")
}
- fun onDiscordClick() {
+ private fun onEnterSymbol() {
+ view?.navigateToSymbol(loginData)
+ }
+
+ private fun onDiscordClick() {
view?.openDiscordInvite()
}
- fun onEmailClick() {
- view?.openEmail(lastError?.message.ifNullOrBlank { "empty" })
+ private fun onEmailClick() {
+ view?.openEmail(lastError?.message.ifNullOrBlank {
+ loginData.baseUrl + "/" + loginData.symbol + "\n" + registerUser.symbols.filterNot {
+ (it.error is AccountPermissionException || it.error is InvalidSymbolException) && it.symbol != loginData.symbol
+ }.joinToString(";\n") { symbol ->
+ buildString {
+ append(" -")
+ append(symbol.symbol)
+ append("(${symbol.error?.message?.let { it.take(46) + "..." } ?: symbol.schools.size})")
+ if (symbol.schools.isNotEmpty()) {
+ append(": ")
+ }
+ append(symbol.schools.joinToString(", ") { unit ->
+ buildString {
+ append(unit.schoolShortName)
+ append("(${unit.error?.message?.let { it.take(46) + "..." } ?: unit.students.size})")
+ }
+ })
+ }
+ } + "\nPozostałe: " + registerUser.symbols.filter {
+ it.error is AccountPermissionException || it.error is InvalidSymbolException
+ }.joinToString(", ") { it.symbol }
+ })
}
private fun logRegisterEvent(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt
index f2acd76c5..39f312bf3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt
@@ -1,15 +1,19 @@
package io.github.wulkanowy.ui.modules.login.studentselect
-import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.ui.base.BaseView
+import io.github.wulkanowy.ui.modules.login.LoginData
interface LoginStudentSelectView : BaseView {
+ val symbols: Map
+
fun initView()
- fun updateData(data: List>)
+ fun updateData(data: List)
- fun openMainView()
+ fun navigateToSymbol(loginData: LoginData)
+
+ fun navigateToNext()
fun showProgress(show: Boolean)
@@ -17,8 +21,6 @@ interface LoginStudentSelectView : BaseView {
fun enableSignIn(enable: Boolean)
- fun showContact(show: Boolean)
-
fun openDiscordInvite()
fun openEmail(lastError: String)
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 58bdf6cef..692aaeb76 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
@@ -12,16 +12,13 @@ import androidx.core.text.parseAsHtml
import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.pojos.RegisterUser
+import io.github.wulkanowy.data.repositories.PreferencesRepository
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
-import io.github.wulkanowy.utils.openInternetBrowser
-import io.github.wulkanowy.utils.showSoftInput
+import io.github.wulkanowy.utils.*
import javax.inject.Inject
@AndroidEntryPoint
@@ -34,6 +31,9 @@ class LoginSymbolFragment :
@Inject
lateinit var appInfo: AppInfo
+ @Inject
+ lateinit var preferencesRepository: PreferencesRepository
+
companion object {
private const val SAVED_LOGIN_DATA = "LOGIN_DATA"
@@ -42,6 +42,8 @@ class LoginSymbolFragment :
}
}
+ override val symbolValue: String? get() = binding.loginSymbolName.text?.toString()
+
override val symbolNameError: CharSequence?
get() = binding.loginSymbolNameLayout.error
@@ -50,7 +52,7 @@ class LoginSymbolFragment :
binding = FragmentLoginSymbolBinding.bind(view)
presenter.onAttachView(
view = this,
- loginData = requireArguments().getSerializable(SAVED_LOGIN_DATA) as LoginData,
+ loginData = requireArguments().serializable(SAVED_LOGIN_DATA),
)
}
@@ -58,7 +60,7 @@ class LoginSymbolFragment :
(requireActivity() as LoginActivity).showActionBar(true)
with(binding) {
- loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) }
+ loginSymbolSignIn.setOnClickListener { presenter.attemptLogin() }
loginSymbolFaq.setOnClickListener { presenter.onFaqClick() }
loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() }
@@ -91,10 +93,21 @@ class LoginSymbolFragment :
}
}
- override fun setErrorSymbolRequire() {
- binding.loginSymbolNameLayout.apply {
+ override fun setErrorSymbolInvalid() {
+ with(binding.loginSymbolNameLayout) {
requestFocus()
- error = getString(R.string.error_field_required)
+ error = getString(R.string.login_invalid_symbol)
+ }
+ }
+
+ override fun setErrorSymbolRequire() {
+ setErrorSymbol(getString(R.string.error_field_required))
+ }
+
+ override fun setErrorSymbol(message: String) {
+ with(binding.loginSymbolNameLayout) {
+ requestFocus()
+ error = message
}
}
@@ -125,8 +138,8 @@ class LoginSymbolFragment :
binding.loginSymbolContainer.visibility = if (show) VISIBLE else GONE
}
- override fun navigateToStudentSelect(studentsWithSemesters: List) {
- (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
+ override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
+ (activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser)
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -159,8 +172,9 @@ class LoginSymbolFragment :
R.string.login_email_text,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
- appInfo.versionName,
+ "${appInfo.versionName}-${appInfo.buildFlavor}",
"$host/${binding.loginSymbolName.text}",
+ preferencesRepository.installationId,
lastError
)
)
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 691cd4481..03ea95fa6 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,9 +1,13 @@
package io.github.wulkanowy.ui.modules.login.symbol
import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.onResourceNotLoading
+import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
+import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
+import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
@@ -23,9 +27,14 @@ class LoginSymbolPresenter @Inject constructor(
lateinit var loginData: LoginData
+ private var registerUser: RegisterUser? = null
+
fun onAttachView(view: LoginSymbolView, loginData: LoginData) {
super.onAttachView(view)
this.loginData = loginData
+ loginErrorHandler.onBadCredentials = {
+ view.setErrorSymbol(it.orEmpty())
+ }
with(view) {
initView()
showContact(false)
@@ -39,21 +48,25 @@ class LoginSymbolPresenter @Inject constructor(
view?.apply { if (symbolNameError != null) clearSymbolError() }
}
- fun attemptLogin(symbol: String) {
- if (symbol.isBlank()) {
+ fun attemptLogin() {
+ if (view?.symbolValue.isNullOrBlank()) {
view?.setErrorSymbolRequire()
return
}
+ loginData = loginData.copy(
+ symbol = view?.symbolValue?.getNormalizedSymbol(),
+ )
resourceFlow {
- studentRepository.getStudentsScrapper(
+ studentRepository.getUserSubjectsFromScrapper(
email = loginData.login,
password = loginData.password,
scrapperBaseUrl = loginData.baseUrl,
- symbol = symbol,
+ symbol = loginData.symbol.orEmpty(),
)
- }.onEach {
- when (it) {
+ }.onEach { user ->
+ registerUser = user.dataOrNull
+ when (user) {
is Resource.Loading -> view?.run {
Timber.i("Login with symbol started")
hideSoftKeyboard()
@@ -61,7 +74,7 @@ class LoginSymbolPresenter @Inject constructor(
showContent(false)
}
is Resource.Success -> {
- when (it.data.size) {
+ when (user.data.symbols.size) {
0 -> {
Timber.i("Login with symbol result: Empty student list")
view?.run {
@@ -70,16 +83,26 @@ class LoginSymbolPresenter @Inject constructor(
}
}
else -> {
- Timber.i("Login with symbol result: Success")
- view?.navigateToStudentSelect(requireNotNull(it.data))
+ val enteredSymbolDetails = user.data.symbols
+ .firstOrNull()
+ ?.takeIf { it.symbol == loginData.symbol }
+
+ if (enteredSymbolDetails?.error is InvalidSymbolException) {
+ view?.run {
+ setErrorSymbolInvalid()
+ showContact(true)
+ }
+ } else {
+ Timber.i("Login with symbol result: Success")
+ view?.navigateToStudentSelect(loginData, requireNotNull(user.data))
+ }
}
}
analytics.logEvent(
"registration_symbol",
"success" to true,
- "students" to it.data.size,
"scrapperBaseUrl" to loginData.baseUrl,
- "symbol" to symbol,
+ "symbol" to view?.symbolValue,
"error" to "No error"
)
}
@@ -90,11 +113,11 @@ class LoginSymbolPresenter @Inject constructor(
"success" to false,
"students" to -1,
"scrapperBaseUrl" to loginData.baseUrl,
- "symbol" to symbol,
- "error" to it.error.message.ifNullOrBlank { "No message" }
+ "symbol" to view?.symbolValue,
+ "error" to user.error.message.ifNullOrBlank { "No message" }
)
- loginErrorHandler.dispatch(it.error)
- lastError = it.error
+ loginErrorHandler.dispatch(user.error)
+ lastError = user.error
view?.showContact(true)
}
}
@@ -111,6 +134,12 @@ class LoginSymbolPresenter @Inject constructor(
}
fun onEmailClick() {
- view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank { "empty" })
+ view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank {
+ registerUser?.symbols?.flatMap { symbol ->
+ symbol.schools.map { it.error?.message } + symbol.error?.message
+ }?.filterNotNull()?.distinct()?.joinToString(";") {
+ it.take(46) + "..."
+ } ?: "blank"
+ })
}
}
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 527895b77..6585c00f4 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
@@ -1,10 +1,13 @@
package io.github.wulkanowy.ui.modules.login.symbol
-import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.ui.base.BaseView
+import io.github.wulkanowy.ui.modules.login.LoginData
interface LoginSymbolView : BaseView {
+ val symbolValue: String?
+
val symbolNameError: CharSequence?
fun initView()
@@ -13,8 +16,12 @@ interface LoginSymbolView : BaseView {
fun setErrorSymbolIncorrect()
+ fun setErrorSymbolInvalid()
+
fun setErrorSymbolRequire()
+ fun setErrorSymbol(message: String)
+
fun clearSymbolError()
fun clearAndFocusSymbol()
@@ -27,7 +34,7 @@ interface LoginSymbolView : BaseView {
fun showContent(show: Boolean)
- fun navigateToStudentSelect(studentsWithSemesters: List)
+ fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser)
fun showContact(show: Boolean)
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 5cd6fa103..d332ee350 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
@@ -6,6 +6,8 @@ import android.os.Build.VERSION_CODES.P
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.addCallback
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
@@ -50,6 +52,8 @@ class MainActivity : BaseActivity(), MainVie
@Inject
lateinit var appInfo: AppInfo
+ private var onBackCallback: OnBackPressedCallback? = null
+
private var accountMenu: MenuItem? = null
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
@@ -88,6 +92,9 @@ class MainActivity : BaseActivity(), MainVie
this.savedInstanceState = savedInstanceState
messageContainer = binding.mainMessageContainer
updateHelper.messageContainer = binding.mainFragmentContainer
+ onBackCallback = onBackPressedDispatcher.addCallback(this, enabled = false) {
+ presenter.onBackPressed()
+ }
val destination = intent.getStringExtra(EXTRA_START_DESTINATION)
?.takeIf { savedInstanceState == null }
@@ -266,6 +273,7 @@ class MainActivity : BaseActivity(), MainVie
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
navController.pushFragment(fragment)
+ onBackCallback?.isEnabled = !isRootView
}
override fun popView(depth: Int) {
@@ -273,10 +281,7 @@ class MainActivity : BaseActivity(), MainVie
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
navController.safelyPopFragments(depth)
- }
-
- override fun onBackPressed() {
- presenter.onBackPressed { super.onBackPressed() }
+ onBackCallback?.isEnabled = !isRootView
}
override fun showStudentAvatar(student: Student) {
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 9c32d8583..458e966d4 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
@@ -139,12 +139,9 @@ class MainPresenter @Inject constructor(
return true
}
- fun onBackPressed(default: () -> Unit) {
+ fun onBackPressed() {
Timber.i("Back pressed in main view")
- view?.run {
- if (isRootView) default()
- else popView()
- }
+ view?.popView()
}
fun onTabSelected(index: Int, wasSelected: Boolean): Boolean {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt
new file mode 100644
index 000000000..59f6d288d
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt
@@ -0,0 +1,81 @@
+package io.github.wulkanowy.ui.modules.message.mailboxchooser
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.DiffUtil.ItemCallback
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.Mailbox
+import io.github.wulkanowy.data.db.entities.MailboxType
+import io.github.wulkanowy.databinding.ItemMailboxChooserBinding
+import javax.inject.Inject
+
+class MailboxChooserAdapter @Inject constructor() :
+ ListAdapter(Differ) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
+ ItemMailboxChooserBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ )
+
+ override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ class ItemViewHolder(
+ private val binding: ItemMailboxChooserBinding,
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(item: MailboxChooserItem) {
+ with(binding) {
+ mailboxItemName.text = item.mailbox?.getFirstLine()
+ ?: root.resources.getString(R.string.message_chip_all_mailboxes)
+ mailboxItemSchool.text = item.mailbox?.getSecondLine()
+ mailboxItemSchool.isVisible = !item.isAll
+
+ root.setOnClickListener { item.onClickListener(item.mailbox) }
+ }
+ }
+
+ private fun Mailbox.getFirstLine() = buildString {
+ if (studentName.isNotBlank() && studentName != userName) {
+ append(studentName)
+ append(" - ")
+ }
+ append(userName)
+ }
+
+ private fun Mailbox.getSecondLine() = buildString {
+ append(schoolNameShort)
+ append(" - ")
+ append(getMailboxType(type))
+ }
+
+ private fun getMailboxType(type: MailboxType): String = when (type) {
+ MailboxType.STUDENT -> R.string.message_mailbox_type_student
+ MailboxType.PARENT -> R.string.message_mailbox_type_parent
+ MailboxType.GUARDIAN -> R.string.message_mailbox_type_guardian
+ MailboxType.EMPLOYEE -> R.string.message_mailbox_type_employee
+ MailboxType.UNKNOWN -> null
+ }.let { it?.let { it1 -> binding.root.resources.getString(it1) }.orEmpty() }
+ }
+
+ private object Differ : ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: MailboxChooserItem,
+ newItem: MailboxChooserItem
+ ): Boolean {
+ return oldItem.mailbox?.globalKey == newItem.mailbox?.globalKey
+ }
+
+ override fun areContentsTheSame(
+ oldItem: MailboxChooserItem,
+ newItem: MailboxChooserItem
+ ): Boolean {
+ return oldItem == newItem
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt
new file mode 100644
index 000000000..37f9a19b5
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt
@@ -0,0 +1,75 @@
+package io.github.wulkanowy.ui.modules.message.mailboxchooser
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.bundleOf
+import androidx.fragment.app.setFragmentResult
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.data.db.entities.Mailbox
+import io.github.wulkanowy.databinding.DialogMailboxChooserBinding
+import io.github.wulkanowy.ui.base.BaseDialogFragment
+import io.github.wulkanowy.utils.parcelableArray
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class MailboxChooserDialog : BaseDialogFragment(), MailboxChooserView {
+
+ @Inject
+ lateinit var presenter: MailboxChooserPresenter
+
+ @Inject
+ lateinit var mailboxAdapter: MailboxChooserAdapter
+
+ companion object {
+ const val LISTENER_KEY = "mailbox_selected"
+ const val MAILBOX_KEY = "selected_mailbox"
+ const val REQUIRED_KEY = "is_mailbox_required"
+
+ fun newInstance(mailboxes: List, isMailboxRequired: Boolean, folder: String) =
+ MailboxChooserDialog().apply {
+ arguments = bundleOf(
+ MAILBOX_KEY to mailboxes.toTypedArray(),
+ REQUIRED_KEY to isMailboxRequired,
+ LISTENER_KEY to folder,
+ )
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setStyle(STYLE_NO_TITLE, 0)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = DialogMailboxChooserBinding.inflate(inflater).apply { binding = this }.root
+
+ @Suppress("UNCHECKED_CAST")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ presenter.onAttachView(
+ view = this,
+ requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false),
+ mailboxes = requireArguments().parcelableArray(MAILBOX_KEY).orEmpty().toList(),
+ )
+ }
+
+ override fun initView() {
+ binding.accountQuickDialogRecycler.adapter = mailboxAdapter
+ }
+
+ override fun submitData(items: List) {
+ mailboxAdapter.submitList(items)
+ }
+
+ override fun onMailboxSelected(item: Mailbox?) {
+ setFragmentResult(
+ requestKey = requireArguments().getString(LISTENER_KEY).orEmpty(),
+ result = bundleOf(MAILBOX_KEY to item),
+ )
+ dismiss()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt
new file mode 100644
index 000000000..6923cf085
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt
@@ -0,0 +1,9 @@
+package io.github.wulkanowy.ui.modules.message.mailboxchooser
+
+import io.github.wulkanowy.data.db.entities.Mailbox
+
+data class MailboxChooserItem(
+ val mailbox: Mailbox? = null,
+ val isAll: Boolean = false,
+ val onClickListener: (Mailbox?) -> Unit,
+)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt
new file mode 100644
index 000000000..5bd7c84ab
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt
@@ -0,0 +1,38 @@
+package io.github.wulkanowy.ui.modules.message.mailboxchooser
+
+import io.github.wulkanowy.data.db.entities.Mailbox
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.ui.base.ErrorHandler
+import timber.log.Timber
+import javax.inject.Inject
+
+class MailboxChooserPresenter @Inject constructor(
+ errorHandler: ErrorHandler,
+ studentRepository: StudentRepository
+) : BasePresenter(errorHandler, studentRepository) {
+
+ fun onAttachView(view: MailboxChooserView, mailboxes: List, requireMailbox: Boolean) {
+ super.onAttachView(view)
+
+ view.initView()
+ Timber.i("Mailbox chooser view was initialized")
+ view.submitData(getMailboxItems(mailboxes, requireMailbox))
+ }
+
+ private fun getMailboxItems(
+ mailboxes: List,
+ requireMailbox: Boolean,
+ ): List = buildList {
+ if (!requireMailbox) {
+ add(MailboxChooserItem(isAll = true, onClickListener = ::onMailboxSelect))
+ }
+ addAll(mailboxes.map {
+ MailboxChooserItem(mailbox = it, isAll = false, onClickListener = ::onMailboxSelect)
+ })
+ }
+
+ fun onMailboxSelect(item: Mailbox?) {
+ view?.onMailboxSelected(item)
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt
new file mode 100644
index 000000000..2e20ee815
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt
@@ -0,0 +1,13 @@
+package io.github.wulkanowy.ui.modules.message.mailboxchooser
+
+import io.github.wulkanowy.data.db.entities.Mailbox
+import io.github.wulkanowy.ui.base.BaseView
+
+interface MailboxChooserView : BaseView {
+
+ fun initView()
+
+ fun submitData(items: List)
+
+ fun onMailboxSelected(item: Mailbox?)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt
index 3c1c53d39..d3c6b95c7 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt
@@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.message.preview
-import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -74,15 +73,20 @@ class MessagePreviewAdapter @Inject constructor() :
}
}
- @SuppressLint("SetTextI18n")
private fun bindMessage(holder: MessageViewHolder, message: Message) {
val context = holder.binding.root.context
+ val recipientCount = (message.unreadBy ?: 0) + (message.readBy ?: 0)
+ val isReceived = message.unreadBy == null
- val readTextValue = when {
- !message.unread -> R.string.all_yes
- else -> R.string.all_no
+ val readText = when {
+ recipientCount > 1 -> {
+ context.getString(R.string.message_read_by, message.readBy, recipientCount)
+ }
+ message.readBy == 1 || (isReceived && !message.unread) -> {
+ context.getString(R.string.message_read, context.getString(R.string.all_yes))
+ }
+ else -> context.getString(R.string.message_read, context.getString(R.string.all_no))
}
- val readText = context.getString(R.string.message_read, context.getString(readTextValue))
with(holder.binding) {
messagePreviewSubject.text = message.subject.ifBlank {
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 2a5523f4d..6c54d9fcb 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
@@ -13,6 +13,7 @@ import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.content.getSystemService
+import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
@@ -23,6 +24,7 @@ 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.message.send.SendMessageActivity
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.shareText
import javax.inject.Inject
@@ -66,13 +68,12 @@ class MessagePreviewFragment :
companion object {
const val MESSAGE_ID_KEY = "message_id"
- fun newInstance(message: Message): MessagePreviewFragment {
- return MessagePreviewFragment().apply {
- arguments = Bundle().apply { putSerializable(MESSAGE_ID_KEY, message) }
- }
+ fun newInstance(message: Message) = MessagePreviewFragment().apply {
+ arguments = bundleOf(MESSAGE_ID_KEY to message)
}
}
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
@@ -83,8 +84,8 @@ class MessagePreviewFragment :
binding = FragmentMessagePreviewBinding.bind(view)
messageContainer = binding.messagePreviewContainer
presenter.onAttachView(
- this,
- (savedInstanceState ?: arguments)?.getSerializable(MESSAGE_ID_KEY) as? Message
+ view = this,
+ message = (savedInstanceState ?: arguments)?.serializable(MESSAGE_ID_KEY),
)
}
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 c011f41f3..56f23b6fa 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
@@ -6,7 +6,6 @@ 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
-import io.github.wulkanowy.data.repositories.MailboxRepository
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
@@ -21,7 +20,6 @@ class MessagePreviewPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val messageRepository: MessageRepository,
- private val mailboxRepository: MailboxRepository,
private val analytics: AnalyticsHelper
) : BasePresenter(errorHandler, studentRepository) {
@@ -187,7 +185,7 @@ class MessagePreviewPresenter @Inject constructor(
presenterScope.launch {
runCatching {
val student = studentRepository.getCurrentStudent(decryptPass = true)
- val mailbox = mailboxRepository.getMailbox(student)
+ val mailbox = messageRepository.getMailboxByStudent(student)
messageRepository.deleteMessage(student, mailbox, message!!)
}
.onFailure {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt
index 334e389e2..14f3d718d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt
@@ -19,11 +19,16 @@ import androidx.core.text.toHtml
import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.databinding.ActivitySendMessageBinding
import io.github.wulkanowy.ui.base.BaseActivity
+import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog
+import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.MAILBOX_KEY
+import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.hideSoftInput
+import io.github.wulkanowy.utils.nullableSerializable
import io.github.wulkanowy.utils.showSoftInput
import javax.inject.Inject
@@ -100,13 +105,17 @@ class SendMessageActivity : BaseActivity
+ presenter.onMailboxSelected(bundle.nullableSerializable(MAILBOX_KEY))
+ }
}
@SuppressLint("ClickableViewAccessibility")
@@ -205,6 +214,14 @@ class SendMessageActivity : BaseActivity) {
+ MailboxChooserDialog.newInstance(
+ mailboxes = mailboxes,
+ isMailboxRequired = true,
+ folder = LISTENER_KEY,
+ ).show(supportFragmentManager, "chooser")
+ }
+
override fun popView() {
finish()
}
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 6c07ee6ad..e776e9941 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,15 +1,15 @@
package io.github.wulkanowy.ui.modules.message.send
-import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType
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.*
-import io.github.wulkanowy.data.resourceFlow
+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.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
@@ -28,7 +28,6 @@ class SendMessagePresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val messageRepository: MessageRepository,
- private val mailboxRepository: MailboxRepository,
private val recipientRepository: RecipientRepository,
private val preferencesRepository: PreferencesRepository,
private val analytics: AnalyticsHelper
@@ -36,10 +35,19 @@ class SendMessagePresenter @Inject constructor(
private val messageUpdateChannel = Channel()
+ private var message: Message? = null
+ private var isReplay: Boolean? = null
+
+ private var mailboxes: List = emptyList()
+ private var selectedMailbox: Mailbox? = null
+
fun onAttachView(view: SendMessageView, reason: String?, message: Message?, reply: Boolean?) {
super.onAttachView(view)
view.initView()
initializeSubjectStream()
+ this.message = message
+ this.isReplay = reply
+
Timber.i("Send message view was initialized")
loadData(message, reply)
with(view) {
@@ -47,7 +55,7 @@ class SendMessagePresenter @Inject constructor(
view.showMessageBackupDialog()
}
reason?.let {
- setSubject("Usprawiedliwenie")
+ setSubject("Usprawiedliwienie")
setContent(it)
}
message?.let {
@@ -110,16 +118,31 @@ class SendMessagePresenter @Inject constructor(
return false
}
+ fun onOpenMailboxChooser() {
+ view?.showMailboxChooser(mailboxes)
+ }
+
+ fun onMailboxSelected(mailbox: Mailbox?) {
+ selectedMailbox = mailbox
+
+ loadData(message, isReplay)
+ }
+
private fun loadData(message: Message?, reply: Boolean?) {
resourceFlow {
val student = studentRepository.getCurrentStudent()
- val mailbox = mailboxRepository.getMailbox(student)
+
+ if (selectedMailbox == null && mailboxes.isEmpty()) {
+ selectedMailbox = messageRepository.getMailboxByStudent(student)
+ mailboxes = messageRepository.getMailboxes(student, false).toFirstResult()
+ .dataOrNull.orEmpty()
+ }
Timber.i("Loading recipients started")
val recipients = createChips(
recipients = recipientRepository.getRecipients(
student = student,
- mailbox = mailbox,
+ mailbox = selectedMailbox,
type = MailboxType.EMPLOYEE,
)
)
@@ -130,7 +153,7 @@ class SendMessagePresenter @Inject constructor(
message != null && reply == true -> recipientRepository.getMessageSender(
student = student,
message = message,
- mailbox = mailbox,
+ mailbox = selectedMailbox,
)
else -> emptyList()
}.let { createChips(it) }
@@ -139,39 +162,42 @@ class SendMessagePresenter @Inject constructor(
messageRecipients.size
)
- Triple(mailbox, recipients, messageRecipients)
+ recipients to messageRecipients
}
.logResourceStatus("load recipients")
- .onEach {
- when (it) {
- is Resource.Loading -> view?.run {
- showProgress(true)
- showContent(false)
- }
- is Resource.Success -> it.data.let { (mailbox, recipientChips, selectedRecipientChips) ->
- view?.run {
- setMailbox(getMailboxName(mailbox))
- setRecipients(recipientChips)
- if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(
- selectedRecipientChips
- )
- showContent(true)
- }
- }
- is Resource.Error -> {
- view?.showContent(true)
- errorHandler.dispatch(it.error)
+ .onResourceLoading {
+ view?.run {
+ showProgress(true)
+ showContent(false)
+ }
+ }
+ .onResourceNotLoading {
+ view?.run { showProgress(false) }
+ }
+ .onResourceError {
+ view?.showContent(true)
+ errorHandler.dispatch(it)
+ }
+ .onResourceSuccess {
+ it.let { (recipientChips, selectedRecipientChips) ->
+ view?.run {
+ setMailbox(getMailboxName(selectedMailbox))
+ setRecipients(recipientChips)
+ if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(
+ selectedRecipientChips
+ )
+ showContent(true)
}
}
- }.onResourceNotLoading {
- view?.run { showProgress(false) }
- }.launch()
+ }
+ .launch()
}
private fun sendMessage(subject: String, content: String, recipients: List) {
+ val mailbox = selectedMailbox ?: return
+
resourceFlow {
val student = studentRepository.getCurrentStudent()
- val mailbox = mailboxRepository.getMailbox(student)
messageRepository.sendMessage(
student = student,
subject = subject,
@@ -222,18 +248,21 @@ class SendMessagePresenter @Inject constructor(
}
}
- private fun getMailboxName(mailbox: Mailbox): String {
+ private fun getMailboxName(mailbox: Mailbox?): String {
+ mailbox ?: return ""
+
+ // username - accountType [\n student name - ] (school short name)
return buildString {
append(mailbox.userName)
append(" - ")
append(getMailboxType(mailbox.type))
+ appendLine()
if (mailbox.type == MailboxType.PARENT) {
- append(" - ")
append(mailbox.studentName)
+ append(" - ")
}
- append(" - ")
append("(${mailbox.schoolNameShort})")
}
}
@@ -267,9 +296,9 @@ class SendMessagePresenter @Inject constructor(
private fun saveDraftMessage() {
messageRepository.draftMessage = MessageDraft(
- view?.formRecipientsData!!,
- view?.formSubjectValue!!,
- view?.formContentValue!!
+ recipients = view?.formRecipientsData!!,
+ subject = view?.formSubjectValue!!,
+ content = view?.formContentValue!!,
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt
index 1057114b8..e27a09d60 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt
@@ -61,4 +61,5 @@ interface SendMessageView : BaseView {
fun getMessageBackupDialogStringWithRecipients(recipients: String): String
fun clearDraft()
+ fun showMailboxChooser(mailboxes: List)
}
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 55f03ef84..6df6153c5 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
@@ -1,28 +1,34 @@
package io.github.wulkanowy.ui.modules.message.tab
+import android.content.res.ColorStateList
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.CompoundButton
import androidx.core.view.isVisible
+import androidx.core.widget.ImageViewCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.ItemMessageBinding
import io.github.wulkanowy.databinding.ItemMessageChipsBinding
+import io.github.wulkanowy.utils.getCompatColor
+import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
class MessageTabAdapter @Inject constructor() :
RecyclerView.Adapter() {
- var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit = { _, _ -> }
+ lateinit var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit
- var onLongItemClickListener: (MessageTabDataItem.MessageItem) -> Unit = {}
+ lateinit var onLongItemClickListener: (MessageTabDataItem.MessageItem) -> Unit
- var onHeaderClickListener: (CompoundButton, Boolean) -> Unit = { _, _ -> }
+ lateinit var onHeaderClickListener: (CompoundButton, Boolean) -> Unit
- var onChangesDetectedListener = {}
+ lateinit var onMailboxClickListener: () -> Unit
+
+ lateinit var onChangesDetectedListener: () -> Unit
private var items = mutableListOf()
@@ -46,12 +52,12 @@ class MessageTabAdapter @Inject constructor() :
val inflater = LayoutInflater.from(parent.context)
return when (MessageItemViewType.values()[viewType]) {
- MessageItemViewType.MESSAGE -> ItemViewHolder(
- ItemMessageBinding.inflate(inflater, parent, false)
- )
MessageItemViewType.FILTERS -> HeaderViewHolder(
ItemMessageChipsBinding.inflate(inflater, parent, false)
)
+ MessageItemViewType.MESSAGE -> ItemViewHolder(
+ ItemMessageBinding.inflate(inflater, parent, false)
+ )
}
}
@@ -66,6 +72,20 @@ class MessageTabAdapter @Inject constructor() :
val item = items[position] as MessageTabDataItem.FilterHeader
with(holder.binding) {
+ chipMailbox.text = item.selectedMailbox
+ ?: root.context.getString(R.string.message_chip_all_mailboxes)
+ chipMailbox.chipBackgroundColor = ColorStateList.valueOf(
+ if (item.selectedMailbox == null) {
+ root.context.getCompatColor(R.color.mtrl_choice_chip_background_color)
+ } else root.context.getThemeAttrColor(android.R.attr.colorPrimary, 64)
+ )
+ chipMailbox.setTextColor(
+ if (item.selectedMailbox == null) {
+ root.context.getThemeAttrColor(android.R.attr.textColorPrimary)
+ } else root.context.getThemeAttrColor(android.R.attr.colorPrimary)
+ )
+ chipMailbox.setOnClickListener { onMailboxClickListener() }
+
if (item.onlyUnread == null) {
chipUnread.isVisible = false
} else {
@@ -74,6 +94,7 @@ class MessageTabAdapter @Inject constructor() :
chipUnread.setOnCheckedChangeListener(onHeaderClickListener)
}
chipUnread.isEnabled = item.isEnabled
+
chipAttachments.isEnabled = item.isEnabled
chipAttachments.isChecked = item.onlyWithAttachments
chipAttachments.setOnCheckedChangeListener(onHeaderClickListener)
@@ -85,21 +106,35 @@ class MessageTabAdapter @Inject constructor() :
val message = item.message
with(holder.binding) {
- val style = if (message.unread) Typeface.BOLD else Typeface.NORMAL
+ val normalFont = Typeface.create("sans-serif", Typeface.NORMAL)
+ val boldFont = Typeface.create("sans-serif-black", Typeface.NORMAL)
+
+ val primaryColor = root.context.getThemeAttrColor(android.R.attr.textColorPrimary)
+ val secondaryColor = root.context.getThemeAttrColor(android.R.attr.textColorSecondary)
+
+ val currentFont = if (message.unread) boldFont else normalFont
+ val currentTextColor = if (message.unread) primaryColor else secondaryColor
with(messageItemAuthor) {
text = message.correspondents
- setTypeface(null, style)
+ setTextColor(currentTextColor)
+ typeface = currentFont
}
- messageItemSubject.run {
+ with(messageItemSubject) {
text = message.subject.ifBlank { context.getString(R.string.message_no_subject) }
- setTypeface(null, style)
+ setTextColor(currentTextColor)
+ typeface = currentFont
}
- messageItemDate.run {
+ with(messageItemDate) {
text = message.date.toFormattedString()
- setTypeface(null, style)
+ setTextColor(currentTextColor)
+ typeface = currentFont
}
- messageItemAttachmentIcon.isVisible = message.hasAttachments
+ with(messageItemAttachmentIcon) {
+ ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(currentTextColor))
+ isVisible = message.hasAttachments
+ }
+ messageItemUnreadIndicator.isVisible = message.unread
root.setOnClickListener {
holder.bindingAdapterPosition.let {
@@ -111,7 +146,7 @@ class MessageTabAdapter @Inject constructor() :
root.setOnLongClickListener {
onLongItemClickListener(item)
- return@setOnLongClickListener true
+ true
}
with(messageItemCheckbox) {
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 634dfc0e7..c0bd4170e 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
@@ -11,6 +11,7 @@ sealed class MessageTabDataItem(val viewType: MessageItemViewType) {
) : MessageTabDataItem(MessageItemViewType.MESSAGE)
data class FilterHeader(
+ val selectedMailbox: String?,
val onlyUnread: Boolean?,
val onlyWithAttachments: Boolean,
val isEnabled: Boolean
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 654b0e226..c78ccc6ec 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
@@ -9,21 +9,26 @@ import android.view.View.*
import android.widget.CompoundButton
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
+import androidx.core.os.bundleOf
import androidx.core.view.updatePadding
+import androidx.fragment.app.setFragmentResultListener
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.databinding.FragmentMessageTabBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.message.MessageFragment
+import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog
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 io.github.wulkanowy.utils.nullableSerializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -40,12 +45,8 @@ class MessageTabFragment : BaseFragment(R.layout.frag
const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id"
- fun newInstance(folder: MessageFolder): MessageTabFragment {
- return MessageTabFragment().apply {
- arguments = Bundle().apply {
- putString(MESSAGE_TAB_FOLDER_ID, folder.name)
- }
- }
+ fun newInstance(folder: MessageFolder) = MessageTabFragment().apply {
+ arguments = bundleOf(MESSAGE_TAB_FOLDER_ID to folder.name)
}
}
@@ -83,6 +84,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag
}
}
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
@@ -104,6 +106,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag
onItemClickListener = presenter::onMessageItemSelected
onLongItemClickListener = presenter::onMessageItemLongSelected
onHeaderClickListener = ::onChipChecked
+ onMailboxClickListener = presenter::onMailboxFilterSelected
onChangesDetectedListener = ::resetListPosition
}
@@ -123,8 +126,15 @@ class MessageTabFragment : BaseFragment(R.layout.frag
messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
+
+ setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle ->
+ presenter.onMailboxSelected(
+ mailbox = bundle.nullableSerializable(MailboxChooserDialog.MAILBOX_KEY),
+ )
+ }
}
+ @Suppress("DEPRECATION")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.action_menu_message_tab, menu)
@@ -246,6 +256,16 @@ class MessageTabFragment : BaseFragment(R.layout.frag
)
}
+ override fun showMailboxChooser(mailboxes: List) {
+ (activity as? MainActivity)?.showDialogFragment(
+ MailboxChooserDialog.newInstance(
+ mailboxes = mailboxes,
+ isMailboxRequired = false,
+ folder = requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!,
+ )
+ )
+ }
+
override fun hideKeyboard() {
activity?.hideSoftInput()
}
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 54711a689..ea142db2b 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,9 +1,9 @@
package io.github.wulkanowy.ui.modules.message.tab
import io.github.wulkanowy.data.*
+import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.enums.MessageFolder
-import io.github.wulkanowy.data.repositories.MailboxRepository
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
@@ -26,7 +26,6 @@ class MessageTabPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val messageRepository: MessageRepository,
- private val mailboxRepository: MailboxRepository,
private val analytics: AnalyticsHelper
) : BasePresenter(errorHandler, studentRepository) {
@@ -36,6 +35,9 @@ class MessageTabPresenter @Inject constructor(
private var lastSearchQuery = ""
+ private var mailboxes: List = emptyList()
+ private var selectedMailbox: Mailbox? = null
+
private var messages = emptyList()
private val searchChannel = Channel()
@@ -122,8 +124,7 @@ class MessageTabPresenter @Inject constructor(
runCatching {
val student = studentRepository.getCurrentStudent(true)
- val mailbox = mailboxRepository.getMailbox(student)
- messageRepository.deleteMessages(student, mailbox, messageList)
+ messageRepository.deleteMessages(student, selectedMailbox, messageList)
}
.onFailure(errorHandler::dispatch)
.onSuccess { view?.showMessagesDeleted() }
@@ -202,13 +203,28 @@ class MessageTabPresenter @Inject constructor(
}
}
+ fun onMailboxFilterSelected() {
+ view?.showMailboxChooser(mailboxes)
+ }
+
+ fun onMailboxSelected(mailbox: Mailbox?) {
+ selectedMailbox = mailbox
+ loadData(false)
+ }
+
private fun loadData(forceRefresh: Boolean) {
Timber.i("Loading $folder message data started")
flatResourceFlow {
val student = studentRepository.getCurrentStudent()
- val mailbox = mailboxRepository.getMailbox(student)
- messageRepository.getMessages(student, mailbox, folder, forceRefresh)
+
+ if (selectedMailbox == null && mailboxes.isEmpty()) {
+ selectedMailbox = messageRepository.getMailboxByStudent(student)
+ mailboxes = messageRepository.getMailboxes(student, forceRefresh).toFirstResult()
+ .dataOrNull.orEmpty()
+ }
+
+ messageRepository.getMessages(student, selectedMailbox, folder, forceRefresh)
}
.logResourceStatus("load $folder message")
.onResourceData {
@@ -327,7 +343,16 @@ class MessageTabPresenter @Inject constructor(
MessageTabDataItem.FilterHeader(
onlyUnread = onlyUnread.takeIf { folder != MessageFolder.SENT },
onlyWithAttachments = onlyWithAttachments,
- isEnabled = !isActionMode
+ isEnabled = !isActionMode,
+ selectedMailbox = selectedMailbox?.let {
+ buildString {
+ if (it.studentName.isNotBlank() && it.studentName != it.userName) {
+ append(it.studentName)
+ append(" - ")
+ }
+ append(it.userName)
+ }
+ },
)
)
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 bfa43b209..6ece6621b 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
@@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.message.tab
+import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.ui.base.BaseView
@@ -46,4 +47,6 @@ interface MessageTabView : BaseView {
fun showActionMode(show: Boolean)
fun showRecyclerBottomPadding(show: Boolean)
+
+ fun showMailboxChooser(mailboxes: List)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt
index 5811456b6..e46ab42cc 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Note
@@ -13,6 +14,7 @@ import io.github.wulkanowy.databinding.DialogNoteBinding
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
class NoteDialog : DialogFragment() {
@@ -25,17 +27,15 @@ class NoteDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
- fun newInstance(exam: Note) = NoteDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ fun newInstance(note: Note) = NoteDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to note)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- note = getSerializable(ARGUMENT_KEY) as Note
- }
+ note = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt
new file mode 100644
index 000000000..163ba8cdf
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt
@@ -0,0 +1,64 @@
+package io.github.wulkanowy.ui.modules.notifications
+
+import android.os.Bundle
+import android.view.View
+import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
+import androidx.appcompat.app.AlertDialog
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.databinding.FragmentNotificationsBinding
+import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.login.LoginActivity
+import io.github.wulkanowy.utils.openNotificationSettings
+
+@AndroidEntryPoint
+class NotificationsFragment :
+ BaseFragment(R.layout.fragment_notifications) {
+
+ private val permission = "android.permission.POST_NOTIFICATIONS"
+
+ private val requestPermissionLauncher = registerForActivityResult(RequestPermission()) {
+ if (it) {
+ navigateToFinish()
+ } else showSettingsDialog()
+ }
+
+ companion object {
+ fun newInstance() = NotificationsFragment()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding = FragmentNotificationsBinding.bind(view)
+ initView()
+ }
+
+ private fun initView() {
+ with(binding) {
+ notificationsSkip.setOnClickListener { navigateToFinish() }
+ notificationsEnable.setOnClickListener { requestPermission() }
+ }
+ }
+
+ private fun showSettingsDialog() {
+ AlertDialog.Builder(requireContext())
+ .setTitle(R.string.notifications_header_title)
+ .setMessage(R.string.notifications_header_description)
+ .setNegativeButton(R.string.notifications_skip) { dialog, _ ->
+ dialog.dismiss()
+ navigateToFinish()
+ }
+ .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ ->
+ requireActivity().openNotificationSettings()
+ }
+ .show()
+ }
+
+ private fun requestPermission() {
+ requestPermissionLauncher.launch(permission)
+ }
+
+ private fun navigateToFinish() {
+ (requireActivity() as LoginActivity).navigateToFinish()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt
index 62f6251ec..46999599b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt
@@ -2,10 +2,10 @@ package io.github.wulkanowy.ui.modules.schoolannouncement
import android.view.LayoutInflater
import android.view.ViewGroup
-import androidx.core.text.parseAsHtml
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.databinding.ItemSchoolAnnouncementBinding
+import io.github.wulkanowy.utils.parseUonetHtml
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
@@ -28,7 +28,7 @@ class SchoolAnnouncementAdapter @Inject constructor() :
with(holder.binding) {
schoolAnnouncementItemDate.text = item.date.toFormattedString()
schoolAnnouncementItemType.text = item.subject
- schoolAnnouncementItemContent.text = item.content.parseAsHtml()
+ schoolAnnouncementItemContent.text = item.content.parseUonetHtml()
root.setOnClickListener { onItemClickListener(item) }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt
index 7dcd51cea..e33a48f03 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.core.text.parseAsHtml
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.parseUonetHtml
+import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
class SchoolAnnouncementDialog : DialogFragment() {
@@ -21,17 +23,15 @@ class SchoolAnnouncementDialog : DialogFragment() {
private const val ARGUMENT_KEY = "item"
- fun newInstance(exam: SchoolAnnouncement) = SchoolAnnouncementDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ fun newInstance(announcement: SchoolAnnouncement) = SchoolAnnouncementDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to announcement)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- announcement = getSerializable(ARGUMENT_KEY) as SchoolAnnouncement
- }
+ announcement = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
@@ -46,7 +46,7 @@ class SchoolAnnouncementDialog : DialogFragment() {
with(binding) {
announcementDialogSubjectValue.text = announcement.subject
announcementDialogDateValue.text = announcement.date.toFormattedString()
- announcementDialogDescriptionValue.text = announcement.content.parseAsHtml()
+ announcementDialogDescriptionValue.text = announcement.content.parseUonetHtml()
announcementDialogClose.setOnClickListener { dismiss() }
}
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 364ad2137..77a3c6cf4 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
@@ -1,18 +1,16 @@
package io.github.wulkanowy.ui.modules.settings.notifications
-import android.annotation.SuppressLint
import android.content.Intent
import android.content.SharedPreferences
-import android.net.Uri
-import android.os.Build
+import android.content.pm.PackageManager
import android.os.Bundle
-import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationManagerCompat
+import androidx.core.content.ContextCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
@@ -26,7 +24,7 @@ import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openInternetBrowser
-import timber.log.Timber
+import io.github.wulkanowy.utils.openNotificationSettings
import javax.inject.Inject
@AndroidEntryPoint
@@ -42,7 +40,14 @@ class NotificationsFragment : PreferenceFragmentCompat(),
override val titleStringId get() = R.string.pref_settings_notifications_title
+ private val notificationsPermission = "android.permission.POST_NOTIFICATIONS"
+
override val isNotificationPermissionGranted: Boolean
+ get() = ContextCompat.checkSelfPermission(
+ requireContext(), notificationsPermission
+ ) == PackageManager.PERMISSION_GRANTED
+
+ override val isNotificationPiggybackPermissionGranted: Boolean
get() {
val packageNameList =
NotificationManagerCompat.getEnabledListenerPackages(requireContext())
@@ -51,6 +56,13 @@ class NotificationsFragment : PreferenceFragmentCompat(),
return appPackageName in packageNameList
}
+ private val requestPermissionLauncher =
+ registerForActivityResult(ActivityResultContracts.RequestPermission()) {
+ if (it) {
+ presenter.onNotificationsPermissionResult()
+ } else openNotificationsPermissionDialog()
+ }
+
private val notificationSettingsPiggybackContract =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
presenter.onNotificationPiggybackPermissionResult()
@@ -156,25 +168,29 @@ class NotificationsFragment : PreferenceFragmentCompat(),
.show()
}
- @SuppressLint("InlinedApi")
override fun openSystemSettings() {
- val intent = if (appInfo.systemVersion >= Build.VERSION_CODES.O) {
- Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
- putExtra("android.provider.extra.APP_PACKAGE", requireActivity().packageName)
- }
- } else {
- Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
- data = Uri.fromParts("package", requireActivity().packageName, null)
- }
- }
- try {
- requireActivity().startActivity(intent)
- } catch (e: Exception) {
- Timber.e(e)
- }
+ requireActivity().openNotificationSettings()
}
- override fun openNotificationPermissionDialog() {
+ override fun requestNotificationPermissions() {
+ requestPermissionLauncher.launch(notificationsPermission)
+ }
+
+ override fun openNotificationsPermissionDialog() {
+ AlertDialog.Builder(requireContext())
+ .setTitle(R.string.notifications_header_title)
+ .setMessage(R.string.notifications_header_description)
+ .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ ->
+ requireActivity().openNotificationSettings()
+ }
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ setNotificationPreferencesChecked(false)
+ }
+ .setOnDismissListener { setNotificationPreferencesChecked(false) }
+ .show()
+ }
+
+ override fun openNotificationPiggyBackPermissionDialog() {
AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.pref_notification_piggyback_popup_title))
.setMessage(getString(R.string.pref_notification_piggyback_popup_description))
@@ -202,6 +218,11 @@ class NotificationsFragment : PreferenceFragmentCompat(),
.show()
}
+ override fun setNotificationPreferencesChecked(isChecked: Boolean) {
+ findPreference(getString(R.string.pref_key_notifications_enable))?.isChecked =
+ isChecked
+ }
+
override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) {
findPreference(getString(R.string.pref_key_notifications_piggyback))?.isChecked =
isChecked
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt
index 4cbdac945..232b03480 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt
@@ -26,12 +26,13 @@ class NotificationsPresenter @Inject constructor(
with(view) {
enableNotification(
- preferencesRepository.notificationsEnableKey,
- preferencesRepository.isServiceEnabled
+ notificationKey = preferencesRepository.notificationsEnableKey,
+ enable = preferencesRepository.isServiceEnabled
)
initView(appInfo.isDebug)
}
+ checkNotificationsPermissionState()
checkNotificationPiggybackState()
Timber.i("Settings notifications view was initialized")
@@ -49,12 +50,17 @@ class NotificationsPresenter @Inject constructor(
view?.openNotificationExactAlarmSettings()
}
}
+ notificationsEnableKey -> {
+ if (isNotificationsEnable && view?.isNotificationPermissionGranted == false) {
+ view?.requestNotificationPermissions()
+ }
+ }
isDebugNotificationEnableKey -> {
chuckerCollector.showNotification = isDebugNotificationEnable
}
isNotificationPiggybackEnabledKey -> {
- if (isNotificationPiggybackEnabled && view?.isNotificationPermissionGranted == false) {
- view?.openNotificationPermissionDialog()
+ if (isNotificationPiggybackEnabled && view?.isNotificationPiggybackPermissionGranted == false) {
+ view?.openNotificationPiggyBackPermissionDialog()
}
}
}
@@ -70,9 +76,15 @@ class NotificationsPresenter @Inject constructor(
view?.openSystemSettings()
}
+ fun onNotificationsPermissionResult() {
+ view?.run {
+ setNotificationPreferencesChecked(isNotificationPermissionGranted)
+ }
+ }
+
fun onNotificationPiggybackPermissionResult() {
view?.run {
- setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
+ setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted)
}
}
@@ -80,10 +92,18 @@ class NotificationsPresenter @Inject constructor(
view?.setUpcomingLessonsNotificationPreferenceChecked(timetableNotificationHelper.canScheduleExactAlarms())
}
+ private fun checkNotificationsPermissionState() {
+ if (preferencesRepository.isNotificationsEnable) {
+ view?.run {
+ setNotificationPreferencesChecked(isNotificationPermissionGranted)
+ }
+ }
+ }
+
private fun checkNotificationPiggybackState() {
if (preferencesRepository.isNotificationPiggybackEnabled) {
view?.run {
- setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
+ setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted)
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt
index 2bf8e31f4..a391681cb 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt
@@ -6,6 +6,8 @@ interface NotificationsView : BaseView {
val isNotificationPermissionGranted: Boolean
+ val isNotificationPiggybackPermissionGranted: Boolean
+
fun initView(showDebugNotificationSwitch: Boolean)
fun showFixSyncDialog()
@@ -14,10 +16,16 @@ interface NotificationsView : BaseView {
fun enableNotification(notificationKey: String, enable: Boolean)
- fun openNotificationPermissionDialog()
+ fun requestNotificationPermissions()
+
+ fun openNotificationsPermissionDialog()
+
+ fun openNotificationPiggyBackPermissionDialog()
fun openNotificationExactAlarmSettings()
+ fun setNotificationPreferencesChecked(isChecked: Boolean)
+
fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean)
fun setUpcomingLessonsNotificationPreferenceChecked(isChecked: Boolean)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt
index 361a59440..598046a2d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt
@@ -8,6 +8,7 @@ import android.view.MenuInflater
import android.view.View
import android.widget.Toast
import androidx.core.content.getSystemService
+import androidx.core.os.bundleOf
import androidx.core.view.get
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
@@ -24,6 +25,8 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.capitalise
import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.nullableSerializable
+import io.github.wulkanowy.utils.serializable
import javax.inject.Inject
@AndroidEntryPoint
@@ -38,7 +41,9 @@ class StudentInfoFragment :
lateinit var studentInfoAdapter: StudentInfoAdapter
override val titleStringId: Int
- get() = when (requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as? StudentInfoView.Type) {
+ get() = when (
+ requireArguments().nullableSerializable(INFO_TYPE_ARGUMENT_KEY)
+ ) {
StudentInfoView.Type.PERSONAL -> R.string.account_personal_data
StudentInfoView.Type.CONTACT -> R.string.account_contact
StudentInfoView.Type.ADDRESS -> R.string.account_address
@@ -58,13 +63,14 @@ class StudentInfoFragment :
fun newInstance(type: StudentInfoView.Type, studentWithSemesters: StudentWithSemesters) =
StudentInfoFragment().apply {
- arguments = Bundle().apply {
- putSerializable(INFO_TYPE_ARGUMENT_KEY, type)
- putSerializable(STUDENT_ARGUMENT_KEY, studentWithSemesters)
- }
+ arguments = bundleOf(
+ INFO_TYPE_ARGUMENT_KEY to type,
+ STUDENT_ARGUMENT_KEY to studentWithSemesters
+ )
}
}
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
@@ -74,9 +80,9 @@ class StudentInfoFragment :
super.onViewCreated(view, savedInstanceState)
binding = FragmentStudentInfoBinding.bind(view)
presenter.onAttachView(
- this,
- requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as StudentInfoView.Type,
- requireArguments().getSerializable(STUDENT_ARGUMENT_KEY) as StudentWithSemesters
+ view = this,
+ type = requireArguments().serializable(INFO_TYPE_ARGUMENT_KEY),
+ studentWithSemesters = requireArguments().serializable(STUDENT_ARGUMENT_KEY),
)
}
@@ -153,7 +159,6 @@ class StudentInfoFragment :
)
}
- @OptIn(ExperimentalStdlibApi::class)
override fun showFamilyTypeData(studentInfo: StudentInfo) {
val items = buildList {
add(studentInfo.firstGuardian?.let {
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 d6917672a..2f0d697fc 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
@@ -191,7 +191,7 @@ class TimetableAdapter @Inject constructor() :
)
} else {
timetableItemDescription.visibility = GONE
- timetableItemRoom.visibility = VISIBLE
+ timetableItemRoom.isVisible = lesson.room.isNotBlank() || lesson.roomOld.isNotBlank()
timetableItemGroup.isVisible = item.showGroupsInPlan && lesson.group.isNotBlank()
timetableItemTeacher.visibility = VISIBLE
}
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 c9243b12e..4f5547d20 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
@@ -8,14 +8,12 @@ import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.databinding.DialogTimetableBinding
-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 io.github.wulkanowy.utils.*
import java.time.Instant
class TimetableDialog : DialogFragment() {
@@ -28,17 +26,15 @@ class TimetableDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
- fun newInstance(exam: Timetable) = TimetableDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ fun newInstance(lesson: Timetable) = TimetableDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to lesson)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- lesson = getSerializable(ARGUMENT_KEY) as Timetable
- }
+ lesson = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
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 fdd4aface..e95d6f827 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
@@ -7,6 +7,7 @@ import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
+import androidx.core.os.bundleOf
import androidx.core.text.parseAsHtml
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
@@ -39,9 +40,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme
private const val ARGUMENT_DATE_KEY = "ARGUMENT_DATE"
fun newInstance(date: LocalDate? = null) = TimetableFragment().apply {
- arguments = Bundle().apply {
- date?.let { putLong(ARGUMENT_DATE_KEY, it.toEpochDay()) }
- }
+ arguments = date?.let { bundleOf(ARGUMENT_DATE_KEY to it.toEpochDay()) }
}
}
@@ -51,6 +50,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt
index 7d32278f0..ddd7488e4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt
@@ -4,10 +4,12 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.databinding.DialogLessonCompletedBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable
+import io.github.wulkanowy.utils.serializable
class CompletedLessonDialog : DialogFragment() {
@@ -19,17 +21,15 @@ class CompletedLessonDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item"
- fun newInstance(exam: CompletedLesson) = CompletedLessonDialog().apply {
- arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
+ fun newInstance(lesson: CompletedLesson) = CompletedLessonDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to lesson)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
- arguments?.run {
- completedLesson = getSerializable(ARGUMENT_KEY) as CompletedLesson
- }
+ completedLesson = requireArguments().serializable(ARGUMENT_KEY)
}
override fun onCreateView(
diff --git a/app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt b/app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt
new file mode 100644
index 000000000..002612a87
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt
@@ -0,0 +1,9 @@
+package io.github.wulkanowy.utils
+
+abstract class BaseRemoteConfigHelper {
+
+ open fun initialize() = Unit
+
+ open val userAgentTemplate: String
+ get() = RemoteConfigDefaults.USER_AGENT_TEMPLATE.value as String
+}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt
new file mode 100644
index 000000000..d0d47025e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt
@@ -0,0 +1,32 @@
+package io.github.wulkanowy.utils
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import java.io.Serializable
+
+inline fun Bundle.serializable(key: String): T = when {
+ Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)!!
+ else -> @Suppress("DEPRECATION") getSerializable(key) as T
+}
+
+inline fun Bundle.nullableSerializable(key: String): T? = when {
+ Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)
+ else -> @Suppress("DEPRECATION") getSerializable(key) as T?
+}
+
+@Suppress("DEPRECATION", "UNCHECKED_CAST")
+inline fun Bundle.parcelableArray(key: String): Array? = when {
+ Build.VERSION.SDK_INT >= 33 -> getParcelableArray(key, T::class.java)
+ else -> getParcelableArray(key) as Array?
+}
+
+inline fun Intent.serializable(key: String): T = when {
+ Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)!!
+ else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T
+}
+
+inline fun Intent.nullableSerializable(key: String): T? = when {
+ Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)
+ else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T?
+}
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 dd91d36d4..cc4c5aaa4 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt
@@ -2,9 +2,11 @@ package io.github.wulkanowy.utils
import android.annotation.SuppressLint
import android.content.Context
+import android.content.res.ColorStateList
import android.graphics.*
import android.text.TextPaint
import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.widget.ImageView
import androidx.annotation.*
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
@@ -12,6 +14,7 @@ import androidx.core.graphics.applyCanvas
import androidx.core.graphics.drawable.RoundedBitmapDrawable
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.graphics.drawable.toBitmap
+import androidx.core.widget.ImageViewCompat
@ColorInt
@@ -85,3 +88,7 @@ fun Context.createNameInitialsDrawable(
return RoundedBitmapDrawableFactory.create(this.resources, bitmap)
.apply { isCircular = true }
}
+
+fun ImageView.setTint(@ColorInt color: Int) {
+ ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color))
+}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
index 43cecd400..a4c2537ac 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
@@ -15,16 +15,17 @@ import java.net.ConnectException
import java.net.SocketException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
+import java.security.cert.CertPathValidatorException
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 ConnectException,
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
@@ -42,10 +43,10 @@ fun Resources.getErrorString(error: Throwable): String = when (error) {
fun Throwable.isShouldBeReported(): Boolean = when (this) {
is UnknownHostException,
+ is ConnectException,
is SocketException,
is SocketTimeoutException,
is InterruptedIOException,
- is ConnectException,
is StreamResetException,
is ServiceUnavailableException,
is FeatureDisabledException,
@@ -70,5 +71,6 @@ private fun Throwable?.isCausedByCertificateNotValidNow(): Boolean {
private fun Throwable?.isCertificateNotValidNow(): Boolean {
val isNotYetValid = this is CertificateNotYetValidException
val isExpired = this is CertificateExpiredException
- return isNotYetValid || isExpired
+ val isInvalidPath = this is CertPathValidatorException
+ return isNotYetValid || isExpired || isInvalidPath
}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt
index ff65d6376..61924d4e9 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt
@@ -4,6 +4,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.enums.GradeColorTheme
+import io.github.wulkanowy.sdk.scrapper.grades.getGradeValueWithModifier
import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
fun List.calcAverage(isOptionalArithmeticAverage: Boolean): Double {
@@ -20,20 +21,15 @@ fun List.calcAverage(isOptionalArithmeticAverage: Boolean): Double {
}
fun List.calcFinalAverage(plusModifier: Double, minusModifier: Double) = asSequence()
- .mapNotNull {
- if (it.finalGrade.matches("[0-6][+-]?".toRegex())) {
- when {
- it.finalGrade.endsWith('+') -> {
- it.finalGrade.removeSuffix("+").toDouble() + plusModifier
- }
- it.finalGrade.endsWith('-') -> {
- it.finalGrade.removeSuffix("-").toDouble() - minusModifier
- }
- else -> {
- it.finalGrade.toDouble()
- }
- }
- } else null
+ .mapNotNull { summary ->
+ val (gradeValue, gradeModifier) = getGradeValueWithModifier(summary.finalGrade)
+ if (gradeValue == null || gradeModifier == null) return@mapNotNull null
+
+ when {
+ gradeModifier > 0 -> gradeValue + plusModifier
+ gradeModifier < 0 -> gradeValue - minusModifier
+ else -> gradeValue + 0.0
+ }
}
.average()
.let { if (it.isNaN()) 0.0 else it }
diff --git a/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt
index 1ef03f2e6..62b85af4d 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt
@@ -1,11 +1,15 @@
package io.github.wulkanowy.utils
+import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.os.Build
import android.provider.CalendarContract
+import android.provider.Settings
import io.github.wulkanowy.BuildConfig
+import timber.log.Timber
import java.time.LocalDateTime
import java.time.ZoneId
@@ -86,6 +90,23 @@ fun Context.openDialer(phone: String) {
}
}
+fun Activity.openNotificationSettings() {
+ val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
+ putExtra("android.provider.extra.APP_PACKAGE", packageName)
+ }
+ } else {
+ Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
+ data = Uri.fromParts("package", packageName, null)
+ }
+ }
+ try {
+ startActivity(intent)
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+}
+
fun Context.shareText(text: String, subject: String?) {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
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 c69fec65c..93e67be01 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt
@@ -4,6 +4,7 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.SharedPrefProvider
+import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder
@@ -25,8 +26,8 @@ fun getRefreshKey(name: String, student: Student): String {
return "${name}_${student.userLoginId}"
}
-fun getRefreshKey(name: String, student: Student, folder: MessageFolder): String {
- return "${name}_${student.id}_${folder.id}"
+fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): String {
+ return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}"
}
class AutoRefreshHelper @Inject constructor(
diff --git a/app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt b/app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt
new file mode 100644
index 000000000..6e8f7ae65
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt
@@ -0,0 +1,8 @@
+package io.github.wulkanowy.utils
+
+enum class RemoteConfigDefaults(val value: Any) {
+ USER_AGENT_TEMPLATE(""),
+ ;
+
+ val key get() = name.lowercase()
+}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt
index 6e11a8b2c..380d6bf6e 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt
@@ -15,5 +15,8 @@ fun List.getCurrentOrLast(): Semester {
// when there is more than one current semester - find one with higher id
singleOrNull { semester -> semester.semesterId == maxByOrNull { it.semesterId }?.semesterId }?.let { return it }
+ // when there is no active kindergarten semester - get one from last year
+ singleOrNull { semester -> semester.schoolYear == maxByOrNull { it.schoolYear }?.schoolYear }?.let { return it }
+
throw IllegalArgumentException("Duplicated last semester! Semesters: ${joinToString(separator = "\n")}")
}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt
index bddd7df4c..8043e3659 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt
@@ -1,7 +1,15 @@
package io.github.wulkanowy.utils
+import androidx.core.text.parseAsHtml
+import org.apache.commons.text.StringEscapeUtils
+
inline fun String?.ifNullOrBlank(defaultValue: () -> String) =
if (isNullOrBlank()) defaultValue() else this
fun String.capitalise() =
- replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
\ No newline at end of file
+ replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
+
+fun String.parseUonetHtml() = this
+ .let(StringEscapeUtils::unescapeHtml4)
+ .replace("\n", "
")
+ .parseAsHtml()
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 3eb42eb97..b3fec438f 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,9 +1,7 @@
-Wersja 1.7.0
+Wersja 1.9.2
-- naprawiliśmy logowanie do aplikacji
-- dodaliśmy wsparcie nowego modułu Wiadomości Plus
-- dodaliśmy nową możliwość wsparcia naszego projektu przez opcjonalne reklamy
-- dodaliśmy sortowanie po średniej
-- naprawiliśmy też kilka usterek wpływających na komfort używania aplikacji
+- naprawiliśmy oznaczanie wiadomości jako odczytanych (problem dotyczył głównie kont rodziców z wieloma dziećmi w tej samej szkole)
+- naprawiliśmy zapisywanie załączników do wiadomości w sytuacji, gdy ten sam załącznik był dodany do więcej niż jednej wiadomości
+- usprawniliśmy ekran z wyborem uczniów i wpisywaniem symbolu przy pierwszym logowaniu
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
diff --git a/app/src/main/res/drawable/ic_circle.xml b/app/src/main/res/drawable/ic_circle.xml
new file mode 100644
index 000000000..d2932fe62
--- /dev/null
+++ b/app/src/main/res/drawable/ic_circle.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_error_filled.xml b/app/src/main/res/drawable/ic_error_filled.xml
new file mode 100644
index 000000000..61b575dc6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_error_filled.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml b/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml
new file mode 100644
index 000000000..b1b01a0b6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground_mono.xml b/app/src/main/res/drawable/ic_launcher_foreground_mono.xml
new file mode 100644
index 000000000..e2e747316
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground_mono.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_send_message.xml b/app/src/main/res/layout/activity_send_message.xml
index 320782bdc..a8041d61c 100644
--- a/app/src/main/res/layout/activity_send_message.xml
+++ b/app/src/main/res/layout/activity_send_message.xml
@@ -55,17 +55,29 @@
android:id="@+id/sendMessageFrom"
android:layout_width="0dp"
android:layout_height="58dp"
- android:layout_marginStart="16dp"
- android:layout_marginLeft="16dp"
- android:layout_marginEnd="16dp"
- android:layout_marginRight="16dp"
+ android:layout_marginStart="8dp"
+ android:background="?selectableItemBackground"
android:gravity="center_vertical"
+ android:paddingStart="8dp"
+ android:paddingEnd="32dp"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/sendMessageFromHint"
app:layout_constraintTop_toTopOf="parent"
tools:text="Jan Kowalski" />
+
+
@@ -26,9 +26,9 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="17dp"
android:layout_marginTop="8dp"
- android:text="I am over 18 years old"
+ android:text="@string/pref_ads_over_18_years_old"
android:textColor="?android:textColorSecondary"
- android:textSize="14dp"
+ android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/ads_consent_privacy" />
@@ -73,7 +73,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
- android:text="Cancel"
+ android:text="@android:string/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0" />
diff --git a/app/src/main/res/layout/dialog_conference.xml b/app/src/main/res/layout/dialog_conference.xml
index d08edf4f7..b6e811ae6 100644
--- a/app/src/main/res/layout/dialog_conference.xml
+++ b/app/src/main/res/layout/dialog_conference.xml
@@ -40,7 +40,7 @@
android:layout_marginStart="0dp"
android:layout_marginTop="28dp"
android:layout_marginEnd="24dp"
- android:text="@string/all_title"
+ android:text="@string/conference_place"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
@@ -71,7 +71,7 @@
android:layout_marginStart="0dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
- android:text="@string/all_subject"
+ android:text="@string/conference_topic"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_grade.xml b/app/src/main/res/layout/dialog_grade.xml
index 9c52c1d0b..94facb232 100644
--- a/app/src/main/res/layout/dialog_grade.xml
+++ b/app/src/main/res/layout/dialog_grade.xml
@@ -21,7 +21,7 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_account_details.xml b/app/src/main/res/layout/fragment_account_details.xml
index af9564b5e..0c904c07d 100644
--- a/app/src/main/res/layout/fragment_account_details.xml
+++ b/app/src/main/res/layout/fragment_account_details.xml
@@ -111,9 +111,11 @@
-
-
+ android:orientation="vertical"
+ tools:context=".ui.modules.login.studentselect.LoginStudentSelectFragment">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ tools:itemCount="33"
+ tools:listitem="@layout/item_login_student_select_student" />
+
+
diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml
new file mode 100644
index 000000000..8e506e1e7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_notifications.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_dashboard_account.xml b/app/src/main/res/layout/item_dashboard_account.xml
index 139157c8b..6e5aca543 100644
--- a/app/src/main/res/layout/item_dashboard_account.xml
+++ b/app/src/main/res/layout/item_dashboard_account.xml
@@ -26,27 +26,31 @@
+ tools:text="Szkoła Wulkanowego" />
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/item_dashboard_horizontal_group.xml b/app/src/main/res/layout/item_dashboard_horizontal_group.xml
index 1d43d5115..0c59d1ebf 100644
--- a/app/src/main/res/layout/item_dashboard_horizontal_group.xml
+++ b/app/src/main/res/layout/item_dashboard_horizontal_group.xml
@@ -37,9 +37,25 @@
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
+ app:layout_goneMarginEnd="16dp"
app:tint="?colorOnSurface"
tools:ignore="ContentDescription" />
+
+
+
+
+ tools:text="16"
+ tools:visibility="visible" />
@@ -145,9 +178,25 @@
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
+ app:layout_goneMarginEnd="16dp"
app:tint="?colorOnSurface"
tools:ignore="ContentDescription" />
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/item_login_student_select_empty_symbol_header.xml b/app/src/main/res/layout/item_login_student_select_empty_symbol_header.xml
new file mode 100644
index 000000000..be0fd905c
--- /dev/null
+++ b/app/src/main/res/layout/item_login_student_select_empty_symbol_header.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_login_student_select_header_school.xml b/app/src/main/res/layout/item_login_student_select_header_school.xml
new file mode 100644
index 000000000..30a8bbf0b
--- /dev/null
+++ b/app/src/main/res/layout/item_login_student_select_header_school.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_login_student_select_header_symbol.xml b/app/src/main/res/layout/item_login_student_select_header_symbol.xml
new file mode 100644
index 000000000..cc1bf709d
--- /dev/null
+++ b/app/src/main/res/layout/item_login_student_select_header_symbol.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_login_student_select_help.xml b/app/src/main/res/layout/item_login_student_select_help.xml
new file mode 100644
index 000000000..b6d81c7cd
--- /dev/null
+++ b/app/src/main/res/layout/item_login_student_select_help.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_login_student_select.xml b/app/src/main/res/layout/item_login_student_select_student.xml
similarity index 71%
rename from app/src/main/res/layout/item_login_student_select.xml
rename to app/src/main/res/layout/item_login_student_select_student.xml
index 1003636fc..d071b1bbf 100644
--- a/app/src/main/res/layout/item_login_student_select.xml
+++ b/app/src/main/res/layout/item_login_student_select_student.xml
@@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
- android:minHeight="72dp"
+ android:minHeight="56dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
tools:context=".ui.modules.login.studentselect.LoginStudentSelectAdapter">
@@ -14,9 +14,10 @@
android:layout_width="32dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
- android:layout_marginStart="12dp"
+ android:layout_marginStart="32dp"
android:layout_marginEnd="28dp"
android:background="@android:color/transparent"
+ android:clickable="false"
tools:text=" " />
-
-
+ android:textSize="14sp" />
diff --git a/app/src/main/res/layout/item_mailbox_chooser.xml b/app/src/main/res/layout/item_mailbox_chooser.xml
new file mode 100644
index 000000000..7c93199bc
--- /dev/null
+++ b/app/src/main/res/layout/item_mailbox_chooser.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml
index c25faacc8..39fbaad01 100644
--- a/app/src/main/res/layout/item_message.xml
+++ b/app/src/main/res/layout/item_message.xml
@@ -30,6 +30,7 @@
android:layout_marginEnd="10dp"
android:ellipsize="end"
android:singleLine="true"
+ android:textColor="?android:textColorSecondary"
android:textSize="15sp"
app:layout_constraintEnd_toStartOf="@+id/messageItemDate"
app:layout_constraintStart_toEndOf="@id/messageItemCheckbox"
@@ -40,10 +41,13 @@
android:id="@+id/messageItemDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
android:gravity="end"
+ android:textColor="?android:textColorSecondary"
android:textSize="13sp"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/messageItemUnreadIndicator"
app:layout_constraintTop_toTopOf="parent"
+ app:layout_goneMarginEnd="0dp"
tools:text="@tools:sample/date/ddmmyy" />
+
+
diff --git a/app/src/main/res/layout/item_message_chips.xml b/app/src/main/res/layout/item_message_chips.xml
index 481a94835..da2e20311 100644
--- a/app/src/main/res/layout/item_message_chips.xml
+++ b/app/src/main/res/layout/item_message_chips.xml
@@ -1,21 +1,30 @@
-
+ app:layout_constraintTop_toTopOf="parent"
+ app:singleLine="true">
+
+
-
+
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 5011e2356..57cf05ddb 100644
--- a/app/src/main/res/menu/action_menu_message_preview.xml
+++ b/app/src/main/res/menu/action_menu_message_preview.xml
@@ -8,20 +8,6 @@
android:title="@string/message_reply"
app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" />
-
-
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index d59ec8e17..da1bca126 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,4 +2,5 @@
-
\ No newline at end of file
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index d59ec8e17..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 85f6a2c87..000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index dc6ac682d..000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index dd67b8771..000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index b7d82f5d5..000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 7ad000eec..000000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 5f76bb6e1..4a91cc852 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+
+ Symbol najdete na stránce deníku v Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUjistěte se, že jste nastavili správnou variantu deníku v poli Variace deníku UONET+ na první přihlašovací obrazovce
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í
@@ -72,6 +72,14 @@
Obnovit
Žák je už přihlášen
Standardní
+ Jiná místa vyhledávání
+ Nebyli nalezeni žádní aktivní žáci
+ Zadejte jiný symbol
+
+ Povolit oznámení
+ Povolit upozornění, abyste nezmeškali zprávu od učitele nebo o nové známce
+ Přeskočit
+ Zapnout
Manažer účtů
Přihlásit se
@@ -309,9 +317,11 @@
Zpráva neexistuje
Musíte vybrat alespoň 1 příjemce
Obsah zprávy musí mít alespoň 3 znaky
+ Všechny poštovní schránky
Pouze nepřečtené
Pouze s přílohami
Přečtena: %s
+ Přečtena přes: %1$d z %2$d osob
- %1$d zpráva
- %1$d zprávy
@@ -339,6 +349,7 @@
- %1$d vybraných
Zprávy odstraněné
+ Vyberte poštovní schránku
Žádné informace o poznámkách
Body
@@ -482,6 +493,8 @@
Přítomnost na setkání
Agenda
+ Místo
+ Téma
Školní oznámení
Žádná školní oznámení
@@ -720,6 +733,10 @@
Odpovědět s historií zpráv
Vypočítat aritmetický průměr, pokud žádná známka nemá váhu
Podpora
+ Ochrana osobních údajů
+ Souhlasy
+ Souhlas se zpracováním údajů souvisejících s reklamami
+ Zobrazit reklamy v aplikaci
Podívejte se na jednu reklamu pro podporu projektu
Souhlas se zpracováním dat
Jestli chcete sledovat reklamu, musíte souhlasit s podmínkami zpracování údajů v našich Zásadách Ochrany Osobních Údajů
@@ -731,6 +748,9 @@
Volbu můžete kdykoliv změnit v nastavení aplikace. Můžeme použít Vaše data k zobrazení reklam šitých pro vás nebo pomocí méně vašich dat zobrazovat nepřizpůsobené reklamy. Podrobnosti naleznete v našich Zásadách ochrany osobních údajů
Přizpůsobené reklamy
Nepřizpůsobené reklamy
+ Je mi více než 18 let
+ Ano, přizpůsobené reklamy
+ Ano, nepřizpůsobené reklamy
Pokročilé
Vzhled a chování
Oznámení
diff --git a/app/src/main/res/values-da-rDK/preferences_values.xml b/app/src/main/res/values-da-rDK/preferences_values.xml
new file mode 100644
index 000000000..ac2b6e9e5
--- /dev/null
+++ b/app/src/main/res/values-da-rDK/preferences_values.xml
@@ -0,0 +1,65 @@
+
+
+
+ - Light
+ - Dark
+ - Black (AMOLED)
+
+
+ - System language
+ - Polski
+ - English
+ - Pусский
+ - Українська
+ - Deutsch
+ - Čeština
+ - Slovenčina
+
+
+ - 15 minutes
+ - 30 minutes
+ - 1 hour
+ - 2 hours
+ - 6 hours
+ - 12 hours
+ - 24 hours
+
+
+ - 0,00
+ - 0,25
+ - 0,33
+ - 0,5
+ - 0,75
+
+
+ - Alphabetically
+ - By date
+ - By average
+
+
+ - Dzienniczek+
+ - Wulkanowy
+ - Grade colors in register
+
+
+ - Up to 1 at once
+ - Always expanded
+ - Unlimited expansions
+
+
+ - Average of grades only from selected semester
+ - Average of averages from both semesters
+ - Average of grades from the whole year
+
+
+ - Lucky number
+ - Unread messages
+ - Attendance
+ - Lessons
+ - Grades
+ - Homework
+ - School announcements
+ - Exams
+ - Conferences
+
+
diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml
new file mode 100644
index 000000000..667231274
--- /dev/null
+++ b/app/src/main/res/values-da-rDK/strings.xml
@@ -0,0 +1,728 @@
+
+
+
+ Login
+ Wulkanowy
+ Grades
+ Attendance
+ Exams
+ Timetable
+ Settings
+ More
+ About
+ Log viewer
+ Debug
+ Notification debug
+ Contributors
+ Licenses
+ Messages
+ New message
+ New homework
+ Notes and achievements
+ Homework
+ Accounts manager
+ Select account
+ Account details
+ Student info
+ Dashboard
+ Notifications center
+
+ Semester %1$d, %2$d/%3$d
+
+ Sign in with the student or parent account
+ Enter the symbol from the register page for account: <b>%1$s</b>
+ Username
+ Email
+ Login, PESEL or e-mail
+ Password
+ UONET+ register variant
+ Mobile API
+ Scraper
+ Hybrid
+ Token
+ PIN
+ Symbol
+ Sign in
+ Password too short
+ Login details are incorrect
+ %1$s. Make sure the correct UONET+ register variation is selected below
+ Invalid PIN
+ Invalid token
+ Token expired
+ Invalid email
+ Use the assigned login instead of email
+ Use the assigned login or email in @%1$s
+ 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 → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login 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
+ This mode displays the same data as it appears on the register website
+ The combination of the best features of the other two modes. It works faster than scraper and provides features not available in the Mobile API mode. It is in the experimental phase
+ Privacy policy
+ Trouble signing in? Contact us!
+ Email
+ Discord
+ Send email
+ Make sure you select the correct UONET+ register variation!
+ I forgot my password
+ Recover your account
+ Recover
+ Student is already signed in
+ Standard
+ Other search locations
+ No active students found
+ Enter a different symbol
+
+ Enable notifications
+ Enable notifications so you don\'t miss message from teacher or new grade
+ Skip
+ Enable
+
+ Account manager
+ Log in
+ Session expired
+ Session expired, log in again
+ Application support
+ Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time
+ Enable ads
+
+ Grade
+ Semester %d
+ Change semester
+ No grades
+ Weight
+ Weight: %s
+ Comment
+ Number of new ratings: %1$d
+ Average: %1$.2f
+ Points: %s
+ No average
+ Total points
+ Final grade
+ Predicted grade
+ Calculated average
+ How does Calculated Average work?
+ 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.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages
+ How does the Final Average work?
+ 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
+ Final average
+ from %1$d of %2$d subjects
+ Summary
+ Class
+ Mark as read
+ Partial
+ Semester
+ Points
+ Legend
+ Class average: %1$s
+ Your average: %1$s
+ Your grade: %1$s
+ Class
+ Student
+
+ - %d grade
+ - %d grades
+
+
+ - New grade
+ - New grades
+
+
+ - New predicted grade
+ - New predicted grades
+
+
+ - New final grade
+ - New final grades
+
+
+ - You received %1$d grade
+ - You received %1$d grades
+
+
+ - You received %1$d predicted grade
+ - You received %1$d predicted grades
+
+
+ - You received %1$d final grade
+ - You received %1$d final grades
+
+
+ Lesson
+ Room
+ Group
+ Hours
+ Changes
+ No lessons this day
+ %s min
+ %s sec
+ %1$s left
+ in %1$s
+ Finished
+ Now: %s
+ Next: %s
+ Later: %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
+
+ - Timetable change
+ - Timetable changes
+
+
+ - %1$s - %2$d change in timetable
+ - %1$s - %2$d changes in timetable
+
+
+ - %1$d change in timetable
+ - %1$d changes in timetable
+
+
+ - %d change
+ - %d changes
+
+
+ Completed lessons
+ Show completed lessons
+ No info about completed lessons
+ Topic
+ Absence
+ Resources
+
+ 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
+
+ Attendance summary
+ Absent for school reasons
+ Excused absence
+ Unexcused absence
+ Exemption
+ Excused lateness
+ Unexcused lateness
+ Present
+ Deleted
+ Unknown
+ Number of lesson
+ No entries
+ Absence reason (optional)
+ Send
+ Absence excuse request sent successfully!
+ You must select at least one absence!
+ Excuse
+
+ - New attendance
+ - New attendance
+
+
+ - %1$d new attendance
+ - %1$d attendance
+
+
+ - %d attendance
+ - %d attendance
+
+
+ Total
+
+ No exams this week
+ Type
+ Entry date
+
+ - New exam
+ - New exams
+
+
+ - %d new exam
+ - %d new exams
+
+
+ - %d exam
+ - %d exams
+
+
+ Inbox
+ Sent
+ Trash
+ (no subject)
+ No messages
+ From:
+ To:
+ Date: %1$s
+ Reply
+ Forward
+ Select all
+ Unselect all
+ Move to trash
+ Delete permanently
+ Message deleted successfully
+ student
+ parent
+ guardian
+ employee
+ Share
+ Print
+ Subject
+ Content
+ Message sent successfully
+ Message does not exist
+ You need to choose at least 1 recipient
+ The message content must be at least 3 characters
+ All mailboxes
+ Only unread
+ Only with attachments
+ Read: %s
+ Read by: %1$d of %2$d people
+
+ - %1$d message
+ - %1$d messages
+
+
+ - New message
+ - New messages
+
+ Do you want to restore draft message?
+ Do you want to restore draft message with recipients: %s?
+
+ - You received %1$d message
+ - You received %1$d messages
+
+
+ - %1$d selected
+ - %1$d selected
+
+ Messages deleted
+ Choose mailbox
+
+ No info about notes
+ Points
+
+ - %d note
+ - %d notes
+
+
+ - New note
+ - New notes
+
+
+ - You received %1$d note
+ - You received %1$d notes
+
+
+
+ - %d praise
+ - %d praises
+
+
+ - New praise
+ - New praises
+
+
+ - You received %1$d praise
+ - You received %1$d praises
+
+
+
+ - %d neutral note
+ - %d neutral notes
+
+
+ - New neutral note
+ - New neutral notes
+
+
+ - You received %1$d neutral note
+ - You received %1$d neutral notes
+
+
+ No info about homework
+ Mark as done
+ Mark as undone
+ Add homework
+ Homework added successfully
+ Homework deleted successfully
+ Attachments
+
+ - New homework
+ - New homework
+
+
+ - You received %d new homework
+ - You received %d new homework
+
+
+ - %d homework
+ - %d homework
+
+
+ Lucky number
+ Today\'s lucky number is
+ No info about the lucky number
+ Lucky number for today
+ Today\'s lucky number is: %s
+ Show history
+
+ Lucky number history
+ No info about lucky numbers
+
+ Mobile devices
+ No devices
+ Deregister
+ Device removed
+ QR code
+ Token
+ Symbol
+ PIN
+
+ School and teachers
+
+ School
+ No info about school
+ School name
+ School address
+ Telephone
+ Name of headmaster
+ Name of pedagogue
+ Show on map
+ Call
+
+ Teachers
+ No info about teachers
+ No subject
+
+ Conferences
+ No info about conferences
+
+ - %d conference
+ - %d conferences
+
+
+ - New conference
+ - New conferences
+
+
+ - You have %1$d new conference
+ - You have %1$d new conferences
+
+ Present at conference
+ Agenda
+ Place
+ Topic
+
+ School announcements
+ No school announcements
+
+ - %d school announcement
+ - %d school announcements
+
+
+ - New school announcement
+ - New school announcements
+
+
+ - You have %1$d new school announcement
+ - You have %1$d new school announcements
+
+
+ Add account
+ Logout
+ Do you want to log out this student?
+ Student logout
+ Student account
+ Parent account
+ Edit data
+ Accounts manager
+ Select student
+ Family
+ Contact
+ Residence details
+ Personal information
+
+ App version
+ Contributors
+ List of Wulkanowy developers
+ Report a bug
+ Send a bug report via e-mail
+ FAQ
+ Read Frequently Asked Questions
+ Discord server
+ Join the Wulkanowy community
+ Facebook fanpage
+ Twitter page
+ Follow us on twitter
+ Like our facebook fanpage
+ Privacy policy
+ Rules for collecting personal data
+ System settings
+ Open system settings
+ Homepage
+ Visit the website and help develop the application
+ Licenses
+ Licenses of libraries used in the application
+
+ License
+
+ Avatar
+ See more on GitHub
+
+ No info about student or student family
+ Name
+ Second name
+ Gender
+ Polish citizenship
+ Family name
+ Mother\'s and father\'s names
+ Phone
+ Cellphone
+ E-mail
+ Address of residence
+ Address of registration
+ Correspondence address
+ Surname and first name
+ Degree of kinship
+ Address
+ Phones
+ Male
+ Female
+ Last name
+ Guardian
+
+ Nick
+ Add nick
+ Choose avatar color
+
+ Share logs
+ Refresh
+
+ Lessons
+ (Tomorrow)
+ (Today and tomorrow)
+ In a moment:
+ Soon:
+ First:
+ Now:
+ End of lessons
+ Next:
+ Later:
+
+ - %1$d more lesson
+ - %1$d more lessons
+
+ until %1$s
+ No upcoming lessons
+ An error occurred while loading the lessons
+ Homework
+ No homework to do
+ An error occurred while loading the homework
+
+ - %1$d more homework
+ - %1$d more homework
+
+ due %1$s
+ Last grades
+ No new grades
+ An error occurred while loading the grades
+ School announcements
+ No current announcements
+ An error occurred while loading the announcements
+
+ - %1$d more announcement
+ - %1$d more announcements
+
+ Exams
+ No upcoming exams
+ An error occurred while loading the exams
+
+ - %1$d more exam
+ - %1$d more exams
+
+ Conferences
+ No upcoming conferences
+ An error occurred while loading the conferences
+
+ - %1$d more conference
+ - %1$d more conferences
+
+ An error occurred while loading data
+ None
+
+ Check for updates
+ Before reporting a bug, check first if an update with the bug fix is available
+
+ Content
+ Retry
+ Description
+ No description
+ Teacher
+ Date
+ Entry date
+ Color
+ Details
+ Category
+ Close
+ No data
+ Subject
+ Prev
+ Next
+ Search
+ Search…
+ Yes
+ No
+ Save
+ Title
+ Add
+ Copied
+ Undo
+ Change
+ Add to calendar
+
+ No lessons
+ Choose theme
+ Light
+ Dark
+ System Theme
+
+ App
+ Default view
+ Calculated average options
+ Force average calculation by app
+ Show presence
+ Theme
+ Grades expanding
+ Mark current lesson
+ Show groups next to subjects
+ Show chart list in class grades
+ Show subjects without grades
+ Grades color scheme
+ Subjects sorting
+ Language
+ Notifications
+ Other
+ Show notifications
+ Show upcoming lesson notifications
+ Make upcoming lesson notification persistent
+ Turn off when notification is not showing in your watch/band
+ Open system notification settings
+ Fix synchronization & notifications issues
+ 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
+ You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.
+ Go to settings
+ Synchronization
+ Automatic update
+ Suspended on holidays
+ Updates interval
+ Wi-Fi only
+ Sync now
+ Synced!
+ Sync failed
+ Sync in progress
+ Last full sync: %s
+ Value of the plus
+ Value of the minus
+ Reply with message history
+ Show arithmetic average when no weights provided
+ Support
+ Privacy Policy
+ Agreements
+ Consent to processing of data related to ads
+ Show ads in app
+ 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
+ Can we use your data to display ads?
+ You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details
+ Personalized ads
+ Non-personalized ads
+ I am over 18 years old
+ Yes, personalized ads
+ Yes, non-personalized ads
+ Advanced
+ Appearance & Behavior
+ Notifications
+ Synchronization
+ Advertisements
+ Grades
+ Dashboard
+ Tiles visibility
+ Attendance
+ Timetable
+ Grades
+ Calculated average
+ Messages
+ Appearance & Behavior
+ Languages, themes, subjects sorting
+ App notifications, fix problems
+ Notifications
+ Synchronization
+ Automatic update, synchronization interval
+ Plus and minus values, average calculation
+ Advanced
+ App version, contributors, social portals
+ Displaying advertisements, project support
+
+ New grades
+ New homework
+ New conferences
+ New exams
+ Lucky number
+ New messages
+ New notes
+ New school announcements
+ Push notifications
+ Upcoming lessons
+ Debug
+ Timetable change
+ New attendance
+
+ Black
+ Red
+ Blue
+ Green
+ Purple
+ No color
+
+ Download of updates has started…
+ An update has just been downloaded.
+ Restart
+ Update failed! Wulkanowy may not function properly. Consider updating
+
+ 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
+ Maintenance underway UONET + register. Try again later
+ Unknown UONET + register error. Try again later
+ Unknown application error. Please try again later
+ An unexpected error occurred
+ Feature disabled by your school
+ Feature not available. Login in a mode other than Mobile API
+ This field is required
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index ef79cee12..f7b8e7c4d 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
+ The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login screen
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
@@ -72,6 +72,14 @@
Wiederherstellen
Student ist bereits angemeldet
Standard
+ Other search locations
+ No active students found
+ Enter a different symbol
+
+ Enable notifications
+ Enable notifications so you don\'t miss message from teacher or new grade
+ Skip
+ Enable
Kundenbetreuer
Anmelden
@@ -275,9 +283,11 @@
Nachricht nicht vorhanden
Sie müssen mindestens 1 Empfänger auswählen.
Der Inhalt der Nachricht muss mindestens 3 Zeichen lang sein.
+ Alle postfächer
Nur ungelesen
Nur mit Anhängen
Lesen: %s
+ Read by: %1$d of %2$d people
- %1$d Nachricht
- %1$d Nachrichten
@@ -297,6 +307,7 @@
- %1$d ausgewählt
Nachrichten gelöscht
+ Postfach auswählen
Keine Informationen über Eintragen
Punkte
@@ -410,6 +421,8 @@
Teilnahme an einem Meeting
Agenda
+ Place
+ Topic
Schulankündigungen
Keine schulankündigungen
@@ -632,6 +645,10 @@
Antwort mit Nachrichtenhistorie
Arithmetisches Mittel anzeigen, wenn keine Gewichte angegeben sind
Unterstützung
+ Datenschutz-Bestimmungen
+ Vereinbarungen
+ Zustimmung zur Verarbeitung von Daten im Zusammenhang mit Anzeigen
+ Anzeigen in der App anzeigen
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
@@ -643,6 +660,9 @@
Sie können Ihre Wahl jederzeit in den App-Einstellungen ändern. Wir verwenden Ihre Daten, um auf Sie zugeschnittene Anzeigen anzuzeigen oder unter Verwendung weniger Ihrer Daten nicht personalisierte Werbung anzuzeigen. Bitte lesen Sie unsere Datenschutzerklärung für Details
Personalisierte Werbung
keine personalisierte Werbung
+ Ich bin über 18 Jahre alt
+ Ja, personalisierte Werbung
+ Ja, nicht personalisierte Werbung
Erweitert
Aussehen & Verhalten
Benachrichtigungen
diff --git a/app/src/main/res/values-es-rES/preferences_values.xml b/app/src/main/res/values-es-rES/preferences_values.xml
new file mode 100644
index 000000000..ac2b6e9e5
--- /dev/null
+++ b/app/src/main/res/values-es-rES/preferences_values.xml
@@ -0,0 +1,65 @@
+
+
+
+ - Light
+ - Dark
+ - Black (AMOLED)
+
+
+ - System language
+ - Polski
+ - English
+ - Pусский
+ - Українська
+ - Deutsch
+ - Čeština
+ - Slovenčina
+
+
+ - 15 minutes
+ - 30 minutes
+ - 1 hour
+ - 2 hours
+ - 6 hours
+ - 12 hours
+ - 24 hours
+
+
+ - 0,00
+ - 0,25
+ - 0,33
+ - 0,5
+ - 0,75
+
+
+ - Alphabetically
+ - By date
+ - By average
+
+
+ - Dzienniczek+
+ - Wulkanowy
+ - Grade colors in register
+
+
+ - Up to 1 at once
+ - Always expanded
+ - Unlimited expansions
+
+
+ - Average of grades only from selected semester
+ - Average of averages from both semesters
+ - Average of grades from the whole year
+
+
+ - Lucky number
+ - Unread messages
+ - Attendance
+ - Lessons
+ - Grades
+ - Homework
+ - School announcements
+ - Exams
+ - Conferences
+
+
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
new file mode 100644
index 000000000..667231274
--- /dev/null
+++ b/app/src/main/res/values-es-rES/strings.xml
@@ -0,0 +1,728 @@
+
+
+
+ Login
+ Wulkanowy
+ Grades
+ Attendance
+ Exams
+ Timetable
+ Settings
+ More
+ About
+ Log viewer
+ Debug
+ Notification debug
+ Contributors
+ Licenses
+ Messages
+ New message
+ New homework
+ Notes and achievements
+ Homework
+ Accounts manager
+ Select account
+ Account details
+ Student info
+ Dashboard
+ Notifications center
+
+ Semester %1$d, %2$d/%3$d
+
+ Sign in with the student or parent account
+ Enter the symbol from the register page for account: <b>%1$s</b>
+ Username
+ Email
+ Login, PESEL or e-mail
+ Password
+ UONET+ register variant
+ Mobile API
+ Scraper
+ Hybrid
+ Token
+ PIN
+ Symbol
+ Sign in
+ Password too short
+ Login details are incorrect
+ %1$s. Make sure the correct UONET+ register variation is selected below
+ Invalid PIN
+ Invalid token
+ Token expired
+ Invalid email
+ Use the assigned login instead of email
+ Use the assigned login or email in @%1$s
+ 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 → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login 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
+ This mode displays the same data as it appears on the register website
+ The combination of the best features of the other two modes. It works faster than scraper and provides features not available in the Mobile API mode. It is in the experimental phase
+ Privacy policy
+ Trouble signing in? Contact us!
+ Email
+ Discord
+ Send email
+ Make sure you select the correct UONET+ register variation!
+ I forgot my password
+ Recover your account
+ Recover
+ Student is already signed in
+ Standard
+ Other search locations
+ No active students found
+ Enter a different symbol
+
+ Enable notifications
+ Enable notifications so you don\'t miss message from teacher or new grade
+ Skip
+ Enable
+
+ Account manager
+ Log in
+ Session expired
+ Session expired, log in again
+ Application support
+ Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time
+ Enable ads
+
+ Grade
+ Semester %d
+ Change semester
+ No grades
+ Weight
+ Weight: %s
+ Comment
+ Number of new ratings: %1$d
+ Average: %1$.2f
+ Points: %s
+ No average
+ Total points
+ Final grade
+ Predicted grade
+ Calculated average
+ How does Calculated Average work?
+ 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.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages
+ How does the Final Average work?
+ 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
+ Final average
+ from %1$d of %2$d subjects
+ Summary
+ Class
+ Mark as read
+ Partial
+ Semester
+ Points
+ Legend
+ Class average: %1$s
+ Your average: %1$s
+ Your grade: %1$s
+ Class
+ Student
+
+ - %d grade
+ - %d grades
+
+
+ - New grade
+ - New grades
+
+
+ - New predicted grade
+ - New predicted grades
+
+
+ - New final grade
+ - New final grades
+
+
+ - You received %1$d grade
+ - You received %1$d grades
+
+
+ - You received %1$d predicted grade
+ - You received %1$d predicted grades
+
+
+ - You received %1$d final grade
+ - You received %1$d final grades
+
+
+ Lesson
+ Room
+ Group
+ Hours
+ Changes
+ No lessons this day
+ %s min
+ %s sec
+ %1$s left
+ in %1$s
+ Finished
+ Now: %s
+ Next: %s
+ Later: %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
+
+ - Timetable change
+ - Timetable changes
+
+
+ - %1$s - %2$d change in timetable
+ - %1$s - %2$d changes in timetable
+
+
+ - %1$d change in timetable
+ - %1$d changes in timetable
+
+
+ - %d change
+ - %d changes
+
+
+ Completed lessons
+ Show completed lessons
+ No info about completed lessons
+ Topic
+ Absence
+ Resources
+
+ 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
+
+ Attendance summary
+ Absent for school reasons
+ Excused absence
+ Unexcused absence
+ Exemption
+ Excused lateness
+ Unexcused lateness
+ Present
+ Deleted
+ Unknown
+ Number of lesson
+ No entries
+ Absence reason (optional)
+ Send
+ Absence excuse request sent successfully!
+ You must select at least one absence!
+ Excuse
+
+ - New attendance
+ - New attendance
+
+
+ - %1$d new attendance
+ - %1$d attendance
+
+
+ - %d attendance
+ - %d attendance
+
+
+ Total
+
+ No exams this week
+ Type
+ Entry date
+
+ - New exam
+ - New exams
+
+
+ - %d new exam
+ - %d new exams
+
+
+ - %d exam
+ - %d exams
+
+
+ Inbox
+ Sent
+ Trash
+ (no subject)
+ No messages
+ From:
+ To:
+ Date: %1$s
+ Reply
+ Forward
+ Select all
+ Unselect all
+ Move to trash
+ Delete permanently
+ Message deleted successfully
+ student
+ parent
+ guardian
+ employee
+ Share
+ Print
+ Subject
+ Content
+ Message sent successfully
+ Message does not exist
+ You need to choose at least 1 recipient
+ The message content must be at least 3 characters
+ All mailboxes
+ Only unread
+ Only with attachments
+ Read: %s
+ Read by: %1$d of %2$d people
+
+ - %1$d message
+ - %1$d messages
+
+
+ - New message
+ - New messages
+
+ Do you want to restore draft message?
+ Do you want to restore draft message with recipients: %s?
+
+ - You received %1$d message
+ - You received %1$d messages
+
+
+ - %1$d selected
+ - %1$d selected
+
+ Messages deleted
+ Choose mailbox
+
+ No info about notes
+ Points
+
+ - %d note
+ - %d notes
+
+
+ - New note
+ - New notes
+
+
+ - You received %1$d note
+ - You received %1$d notes
+
+
+
+ - %d praise
+ - %d praises
+
+
+ - New praise
+ - New praises
+
+
+ - You received %1$d praise
+ - You received %1$d praises
+
+
+
+ - %d neutral note
+ - %d neutral notes
+
+
+ - New neutral note
+ - New neutral notes
+
+
+ - You received %1$d neutral note
+ - You received %1$d neutral notes
+
+
+ No info about homework
+ Mark as done
+ Mark as undone
+ Add homework
+ Homework added successfully
+ Homework deleted successfully
+ Attachments
+
+ - New homework
+ - New homework
+
+
+ - You received %d new homework
+ - You received %d new homework
+
+
+ - %d homework
+ - %d homework
+
+
+ Lucky number
+ Today\'s lucky number is
+ No info about the lucky number
+ Lucky number for today
+ Today\'s lucky number is: %s
+ Show history
+
+ Lucky number history
+ No info about lucky numbers
+
+ Mobile devices
+ No devices
+ Deregister
+ Device removed
+ QR code
+ Token
+ Symbol
+ PIN
+
+ School and teachers
+
+ School
+ No info about school
+ School name
+ School address
+ Telephone
+ Name of headmaster
+ Name of pedagogue
+ Show on map
+ Call
+
+ Teachers
+ No info about teachers
+ No subject
+
+ Conferences
+ No info about conferences
+
+ - %d conference
+ - %d conferences
+
+
+ - New conference
+ - New conferences
+
+
+ - You have %1$d new conference
+ - You have %1$d new conferences
+
+ Present at conference
+ Agenda
+ Place
+ Topic
+
+ School announcements
+ No school announcements
+
+ - %d school announcement
+ - %d school announcements
+
+
+ - New school announcement
+ - New school announcements
+
+
+ - You have %1$d new school announcement
+ - You have %1$d new school announcements
+
+
+ Add account
+ Logout
+ Do you want to log out this student?
+ Student logout
+ Student account
+ Parent account
+ Edit data
+ Accounts manager
+ Select student
+ Family
+ Contact
+ Residence details
+ Personal information
+
+ App version
+ Contributors
+ List of Wulkanowy developers
+ Report a bug
+ Send a bug report via e-mail
+ FAQ
+ Read Frequently Asked Questions
+ Discord server
+ Join the Wulkanowy community
+ Facebook fanpage
+ Twitter page
+ Follow us on twitter
+ Like our facebook fanpage
+ Privacy policy
+ Rules for collecting personal data
+ System settings
+ Open system settings
+ Homepage
+ Visit the website and help develop the application
+ Licenses
+ Licenses of libraries used in the application
+
+ License
+
+ Avatar
+ See more on GitHub
+
+ No info about student or student family
+ Name
+ Second name
+ Gender
+ Polish citizenship
+ Family name
+ Mother\'s and father\'s names
+ Phone
+ Cellphone
+ E-mail
+ Address of residence
+ Address of registration
+ Correspondence address
+ Surname and first name
+ Degree of kinship
+ Address
+ Phones
+ Male
+ Female
+ Last name
+ Guardian
+
+ Nick
+ Add nick
+ Choose avatar color
+
+ Share logs
+ Refresh
+
+ Lessons
+ (Tomorrow)
+ (Today and tomorrow)
+ In a moment:
+ Soon:
+ First:
+ Now:
+ End of lessons
+ Next:
+ Later:
+
+ - %1$d more lesson
+ - %1$d more lessons
+
+ until %1$s
+ No upcoming lessons
+ An error occurred while loading the lessons
+ Homework
+ No homework to do
+ An error occurred while loading the homework
+
+ - %1$d more homework
+ - %1$d more homework
+
+ due %1$s
+ Last grades
+ No new grades
+ An error occurred while loading the grades
+ School announcements
+ No current announcements
+ An error occurred while loading the announcements
+
+ - %1$d more announcement
+ - %1$d more announcements
+
+ Exams
+ No upcoming exams
+ An error occurred while loading the exams
+
+ - %1$d more exam
+ - %1$d more exams
+
+ Conferences
+ No upcoming conferences
+ An error occurred while loading the conferences
+
+ - %1$d more conference
+ - %1$d more conferences
+
+ An error occurred while loading data
+ None
+
+ Check for updates
+ Before reporting a bug, check first if an update with the bug fix is available
+
+ Content
+ Retry
+ Description
+ No description
+ Teacher
+ Date
+ Entry date
+ Color
+ Details
+ Category
+ Close
+ No data
+ Subject
+ Prev
+ Next
+ Search
+ Search…
+ Yes
+ No
+ Save
+ Title
+ Add
+ Copied
+ Undo
+ Change
+ Add to calendar
+
+ No lessons
+ Choose theme
+ Light
+ Dark
+ System Theme
+
+ App
+ Default view
+ Calculated average options
+ Force average calculation by app
+ Show presence
+ Theme
+ Grades expanding
+ Mark current lesson
+ Show groups next to subjects
+ Show chart list in class grades
+ Show subjects without grades
+ Grades color scheme
+ Subjects sorting
+ Language
+ Notifications
+ Other
+ Show notifications
+ Show upcoming lesson notifications
+ Make upcoming lesson notification persistent
+ Turn off when notification is not showing in your watch/band
+ Open system notification settings
+ Fix synchronization & notifications issues
+ 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
+ You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.
+ Go to settings
+ Synchronization
+ Automatic update
+ Suspended on holidays
+ Updates interval
+ Wi-Fi only
+ Sync now
+ Synced!
+ Sync failed
+ Sync in progress
+ Last full sync: %s
+ Value of the plus
+ Value of the minus
+ Reply with message history
+ Show arithmetic average when no weights provided
+ Support
+ Privacy Policy
+ Agreements
+ Consent to processing of data related to ads
+ Show ads in app
+ 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
+ Can we use your data to display ads?
+ You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details
+ Personalized ads
+ Non-personalized ads
+ I am over 18 years old
+ Yes, personalized ads
+ Yes, non-personalized ads
+ Advanced
+ Appearance & Behavior
+ Notifications
+ Synchronization
+ Advertisements
+ Grades
+ Dashboard
+ Tiles visibility
+ Attendance
+ Timetable
+ Grades
+ Calculated average
+ Messages
+ Appearance & Behavior
+ Languages, themes, subjects sorting
+ App notifications, fix problems
+ Notifications
+ Synchronization
+ Automatic update, synchronization interval
+ Plus and minus values, average calculation
+ Advanced
+ App version, contributors, social portals
+ Displaying advertisements, project support
+
+ New grades
+ New homework
+ New conferences
+ New exams
+ Lucky number
+ New messages
+ New notes
+ New school announcements
+ Push notifications
+ Upcoming lessons
+ Debug
+ Timetable change
+ New attendance
+
+ Black
+ Red
+ Blue
+ Green
+ Purple
+ No color
+
+ Download of updates has started…
+ An update has just been downloaded.
+ Restart
+ Update failed! Wulkanowy may not function properly. Consider updating
+
+ 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
+ Maintenance underway UONET + register. Try again later
+ Unknown UONET + register error. Try again later
+ Unknown application error. Please try again later
+ An unexpected error occurred
+ Feature disabled by your school
+ Feature not available. Login in a mode other than Mobile API
+ This field is required
+
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 566519e8c..4891015d2 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
+ Symbol można znaleźć na stronie dziennika w Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUpewnij się, że ustawiłeś odpowiednią odmianę dziennika w polu Odmiana dziennika UONET+ na pierwszym ekranie logowania
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ń
@@ -72,6 +72,14 @@
Przywróć
Uczeń jest już zalogowany
Standardowa
+ Inne lokalizacje wyszukiwania
+ Nie znaleziono aktywnych uczniów
+ Wprowadź inny symbol
+
+ Włącz powiadomienia
+ Włącz powiadomienia, aby nie przegapić wiadomości od nauczyciela lub nowej oceny
+ Pomiń
+ Włącz
Menadżer kont
Zaloguj się
@@ -309,9 +317,11 @@
Wiadomość nie istnieje
Musisz wybrać co najmniej 1 adresata
Treść wiadomości musi zawierać co najmniej 3 znaki
+ Wszystkie skrzynki
Tylko nieprzeczytane
Tylko z załącznikami
Przeczytana: %s
+ Przeczytana przez: %1$d z %2$d osób
- %1$d wiadomość
- %1$d wiadomości
@@ -339,6 +349,7 @@
- %1$d wybranych
Wiadomości zostały usunięte
+ Wybierz skrzynkę
Brak informacji o uwagach
Punkty
@@ -482,6 +493,8 @@
Obecność na zebraniu
Agenda
+ Miejsce
+ Temat
Ogłoszenia szkolne
Brak ogłoszeń szkolnych
@@ -720,6 +733,10 @@
Odpowiadaj z historią wiadomości
Licz średnią arytmetyczną, gdy żadna ocena nie ma wagi
Wsparcie
+ Polityka prywatności
+ Zgody
+ Zgoda na przetwarzanie danych związanych z reklamami
+ Pokazuj reklamy w aplikacji
Obejrzyj pojedynczą reklamę, aby wesprzeć projekt
Zgoda na przetwarzanie danych
Aby obejrzeć reklamę, musisz zaakceptować warunki przetwarzania danych zawarte w naszej Polityce Prywatności
@@ -731,6 +748,9 @@
Możesz zmienić swój wybór w dowolnym momencie w ustawieniach aplikacji. Możemy wykorzystać Twoje dane do wyświetlania reklam dostosowanych do Ciebie lub, przy użyciu mniejszej ilości danych, wyświetlić niepersonalizowane reklamy. Zobacz naszą Politykę Prywatności, aby uzyskać więcej informacji
Spersonalizowane reklamy
Niespersonalizowane reklamy
+ Mam ukończone 18 lat
+ Tak, spersonalizowane reklamy
+ Tak, niespersonalizowane reklamy
Zaawansowane
Wygląd i zachowanie
Powiadomienia
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index a3c5a62df..01e43183f 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -55,7 +55,7 @@
Неверный symbol
Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+
Данный ученик уже авторизован
- Symbol можно найти на странице регистрации в Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nУбедитесь, что вы выбрали соответствующий тип дневника в поле Тип дневника UONET+ на предыдущем экране
+ The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login screen
Выберите учеников для авторизации в приложении
Другие варианты
В этом режиме не работают: счастливый номер, статистика класса по оценкам, статистика посещаемости и уроков, информация о школе и список зарегистрированных устройств
@@ -72,14 +72,22 @@
Восстановить
Ученик уже авторизован
Стандартный
+ Other search locations
+ No active students found
+ Enter a different symbol
+
+ Enable notifications
+ Enable notifications so you don\'t miss message from teacher or new grade
+ Skip
+ Enable
Менеджер аккаунтов
Войти
Сеанс истёк
Сеанс истёк, авторизуйтесь снова
- Application support
- Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time
- Enable ads
+ Поддержка приложения
+ Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время
+ Включить рекламу
Оценка
%d семестр
@@ -97,7 +105,7 @@
Ожидаемая оценка
Рассчитанная средняя оценка
Как работает \"Рассчитанная средняя оценка\"?
- 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.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages
+ Рассчитанная средняя оценка - это среднее арифметическое, рассчитанное на основе средних оценок по предметам. Это позволяет узнать приблизительную итоговую среднюю оценку. Она рассчитывается способом, выбранным пользователем в настройках приложения. Рекомендуется выбрать подходящий вариант, так как каждая школа по разному считает среднюю оценку. Кроме того, если ваша школа выставляет средние оценки по предметам на странице Vulcan, приложение просто загрузит их. Это можно изменить, заставив приложение считать среднюю оценку в настройках.\n\nСредняя из оценок выбранного семестра:\n1. Вычисление средневзвешенного значения по каждому предмету за семестр\n2.Суммирование вычисленных значений\n3. Вычисление среднего арифметического суммированных значений\n\nСредняя из средних оценок семестров:\n1.Расчет средневзвешенного значения для каждого предмета в семестрах. \n2. Вычисление среднего арифметического из средневзвешенных значений для каждого предмета в семестрах.\n3. Суммирование средних арифметических\n4. Вычисление среднего арифматического из суммированных значений\n\nСредняя из оценок со всего года:\n1. Расчет средневзвешенного значения по каждому предмету за год. Итоговое среднее значение за 1 семестр не имеет значения.\n2. Суммирование вычисленных средних\n3. Расчет среднего арифметического суммированных чисел
Как работает \"Итоговая средняя оценка\"?
Итоговая средняя оценка - это среднее арифметическое, рассчитанное из всех имеющихся на данный момент итоговых оценок в семестре.\n\nРассчет происходит следующим образом:\n1. Суммирование итоговых оценок, выставленных преподавателями\n2. Полученная сумма делится на число предметов, по которым выставлены оценки
Итоговая средняя оценка
@@ -297,10 +305,10 @@
Перенести в корзину
Удалить навсегда
Письмо успешно удалено
- student
- parent
- guardian
- employee
+ ученик
+ родитель
+ опекун
+ работник
Поделиться
Печать
Тема
@@ -309,9 +317,11 @@
Письма не существует
Вы должны выбрать как минимум одного получателя
Текст сообщения должен содержать как минимум 3 знака
+ Все почтовые ящики
Только непрочитанные
Только с вложениями
Прочитано: %s
+ Прочитано: %1$d из %2$d человек
- %1$d сообщение
- %1$d сообщения
@@ -339,6 +349,7 @@
- %1$d выбрано
Сообщение удалено
+ Выбрать почтовый ящик
Нет записей о замечаниях и свершениях
Баллы
@@ -482,6 +493,8 @@
Присутствует на встрече
Повестка дня
+ Place
+ Topic
Объявления школы
Нет школьных объявлений
@@ -720,6 +733,10 @@
Отвечать с историей сообщений
Показывать среднее арифметическое при отсутствии стоимости
Поддержка
+ Политика приватности
+ Соглашения
+ Согласие на обработку данных, связанных с объявлениями
+ Показать рекламу в приложении
Посмотреть рекламу для поддержки проекта
Согласие на обработку данных
Для просмотра рекламы вы должны согласиться с условиями обработки данных нашей Политики конфиденциальности
@@ -727,10 +744,13 @@
Политика конфиденциальности
Реклама загружается
Спасибо за вашу поддержку, возвращайтесь позже для дополнительной рекламы
- Can we use your data to display ads?
- You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details
- Personalized ads
- Non-personalized ads
+ Можем ли мы использовать ваши данные для показа рекламы?
+ Вы можете изменить свой выбор в любое время в настройках приложения. Мы можем использовать ваши данные для показа объявлений в соответствии с вашими пожеланиями или, используя меньше данных, отображать неперсональную рекламу. Пожалуйста, ознакомьтесь с нашей политикой конфиденциальности для подробностей
+ Персонализированная реклама
+ Неперсонализированная реклама
+ Я старше 18 лет
+ Да, персонализировать рекламу
+ Да, не персонализировать рекламу
Расширенные
Внешний вид и поведение
Уведомления
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 6a4505d70..4189c5349 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -35,7 +35,7 @@
Email
Prihlásenie, číslo PESEL alebo e-mail
Heslo
- Variácie denníka UONET+
+ Variácia denníka UONET+
Mobile API
Scraper
Hybridné
@@ -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+
+ Symbol nájdete na stránke denníka v Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUistite sa, že ste nastavili správny variant denníka v poli Variácia denníka UONET+ na prvej prihlasovacej obrazovke
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í
@@ -72,6 +72,14 @@
Obnoviť
Žiak je už prihlásený
Štandardná
+ Iné miesta vyhľadávania
+ Neboli nájdení žiadni aktívni žiaci
+ Zadajte iný symbol
+
+ Povoliť oznámenia
+ Povoliť oznámenia, aby ste nezmeškali správu od učiteľa alebo o novej známke
+ Preskočiť
+ Zapnúť
Manažér účtov
Prihlásiť sa
@@ -309,9 +317,11 @@
Správa neexistuje
Musíte vybrať aspoň 1 príjemca
Obsah správy musí mať aspoň 3 znaky
+ Všetky poštové schránky
Iba neprečítané
Iba s prílohami
Prečítaná: %s
+ Prečítaná cez: %1$d z %2$d osôb
- %1$d správa
- %1$d správy
@@ -339,6 +349,7 @@
- %1$d vybraných
Správy odstránené
+ Vyberte poštovú schránku
Žiadne informácie o poznámkach
Body
@@ -482,6 +493,8 @@
Prítomnosť na stretnutí
Agenda
+ Miesto
+ Téma
Školské oznámenia
Žiadne školské oznámenia
@@ -720,6 +733,10 @@
Odpovedať s históriou správ
Vypočítať aritmetický priemer, ak žiadna známka nemá váhu
Podpora
+ Ochrana osobných údajov
+ Súhlasy
+ Súhlas so spracovaním údajov súvisiacich s reklamami
+ Zobraziť reklamy v aplikácii
Pozrite sa na jednu reklamu pre podporu projektu
Súhlas so spracovaním dát
Ak chcete sledovať reklamu, musíte súhlasiť s podmienkami spracovania údajov v našich Zásadách Ochrany Osobných Údajov
@@ -731,6 +748,9 @@
Voľbu môžete kedykoľvek zmeniť v nastavení aplikácie. Môžeme použiť vaše údaje na zobrazenie reklám šitých pre vás alebo pomocou menej vašich dát zobrazovať neprispôsobené reklamy. Podrobnosti nájdete v našich Zásadách ochrany osobných údajov
Prispôsobené reklamy
Neprispôsobené reklamy
+ Mám viac ako 18 rokov
+ Áno, prispôsobené reklamy
+ Áno, neprispôsobené reklamy
Pokročilé
Vzhľad a správanie
Oznámenia
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 742e800f3..99c340650 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -55,7 +55,7 @@
Неправильний symbol
Студента не знайдено. Перевірте symbol та обранний тип щоденника UONET+
Цього учня вже авторизовано
- Symbol можна знайти на сторінці реєстрації в Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nПереконайтесь, що ви встановили відповідний тип щоденника в полі Тип щоденника UONET+ на попередньому екрані
+ The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login screen
Виберіть учнів для авторизації в додатку
Інші варіанти
У цьому режимі не працюють: щасливий номер, статистика класу по оцінкам, статистика відвідуваності та уроків, інформація про школу та список зареєстрованих пристроїв
@@ -72,14 +72,22 @@
Відновити
Учня вже авторизовано
Стандартний
+ Other search locations
+ No active students found
+ Enter a different symbol
+
+ Enable notifications
+ Enable notifications so you don\'t miss message from teacher or new grade
+ Skip
+ Enable
Змінити облікові записи
Увійти
Минув термін дії сесії
Минув термін дії сесії, авторизуйтеся знову
- Application support
- Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time
- Enable ads
+ Підтримка додатку
+ Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час
+ Увімкнути рекламу
Оцінка
%d семестр
@@ -97,7 +105,7 @@
Передбачувана оцінка
Розрахована середня оцінка
Як працює \"Розрахована середня оцінка\"?
- 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.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages
+ Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів. Це дозволяє дізнатися приблизну кінцеву середню оцінку. Вона розраховується спосібом, обраним користувачем у налаштуваннях програми. Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку. Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки і не розраховує їх самостійно. Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\nСередні оцінки тільки за обраний семестр:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\nСереднє значення з обох семестрів:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\nСереднє значення оцінок за весь рік: \n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх
Як працює \"Підсумкова середня оцінка\"?
Підсумкове середнє значення - це середнє арифметичне, обчислене з усіх наявних наразі підсумкових оцінок у даному семестрі. \n\nСхема обчислення складається з таких кроків:\n1. Сумування підсумкових оцінок, виставленних викладачами\n2. Ділення на кількість предметів, з яких виставлені ці оцінки
Підсумкова середня оцінка
@@ -220,7 +228,7 @@
Всі в серії
Час початку
Час завершення
- Час завершення має бути більшим, ніж час початку
+ Час завершення має бути пізніше часу початку
Підсумок відвідуваності
Відсутність зі шкільних причин
@@ -297,10 +305,10 @@
Перемістити до кошика
Видалити назавжди
Лист було успішно видалено
- student
- parent
- guardian
- employee
+ учень
+ родич
+ опікун
+ працівник
Поділитись
Друк
Тема
@@ -309,9 +317,11 @@
Такого листа не існує
Необхідно обрати принаймні 1 адресата
Зміст листа повинен складатися принаймні з 3 знаків
+ Усі поштові скриньки
Лише непрочитані
Тільки з вкладеннями
Прочитаний: %s
+ Прочитано: %1$d з %2$d осіб
- %1$d лист
- %1$d листи
@@ -338,7 +348,8 @@
- %1$d вибрано
- %1$d вибрано
- Листи видалені
+ Листи видалено
+ Вибрати поштову скриньку
Немає інформації о зауваженнях
Бали
@@ -482,6 +493,8 @@
Присутність на зустрічі
Порядок денний
+ Place
+ Topic
Оголошення школи
Немає шкільних оголошень
@@ -720,6 +733,10 @@
Відповісти з історією повідомлень
Вилічити середню аритметичну, якщо оцінка немає вартості
Підтримка
+ Політика конфіденційності
+ Угоди
+ Згода на обробку даних, пов\'язаних з рекламою
+ Показувати рекламу в додатку
Подивіться одну рекламу для підтримки проєкту
Згода в обробці даних
Щоб переглянути рекламу, ви повинні погодитися з умовами обробки даних нашої Політики конфіденційності
@@ -727,10 +744,13 @@
Політика конфіденційності
Реклама завантажується
Дякуємо за вашу підтримку, повертайтеся пізніше для більшої кількості реклам
- Can we use your data to display ads?
- You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details
- Personalized ads
- Non-personalized ads
+ Чи можемо ми використовувати ваші дані для висвітлювання реклами?
+ Ви можете змінити свій вибір в будь-який час в налаштуваннях додатку. Ми можемо використовувати ваші дані для висвітлювання реклами, адаптованої до вас або, використовуючи менше ваших даних, висвітлювати неперсоналізовану рекламу. Перегляньте нашу Політику конфіденційності для подробиць
+ Персоналізована реклама
+ Неперсоналізована реклама
+ Мені більше 18 років
+ Так, персоналізована реклама
+ Так, неперсоналізована реклама
Додатково
Вигляд та поведінка
Сповіщення
diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml
index 8413d68e4..04f4a12bf 100644
--- a/app/src/main/res/values/api_hosts.xml
+++ b/app/src/main/res/values/api_hosts.xml
@@ -6,8 +6,9 @@
- Gdańska Platforma Edukacyjna
- Lubelski Portal Oświatowy
- EduNet Miasta Tarnowa
- - ResMan Rzeszów
- Platforma Edukacyjna Koszalina
+ - Gmina-miasto Tomaszów Mazowiecki - System zarządzania oświatą
+ - ResMan Rzeszów
- Rawa Mazowiecka - Platforma vEdukacja
- Zduńska Wola - e-Urząd
- Sieradz - Portal oświatowy
@@ -27,7 +28,6 @@
- https://edu.gdansk.pl
- https://edu.lublin.eu
- https://umt.tarnow.pl
- - https://resman.pl
- https://eduportal.koszalin.pl
- https://vulcan.net.pl/?login
- https://vulcan.net.pl/?login
@@ -40,7 +40,9 @@
- https://vulcan.net.pl/?login
- https://vulcan.net.pl/?login
- https://vulcan.net.pl/?login
- - http://fakelog.cf/?email
+ - https://vulcan.net.pl/?login
+ - https://vulcan.net.pl/?login
+ - https://fakelog.cf/?email
- Default
@@ -48,8 +50,9 @@
- gdansk
- lublin
- tarnow
- - rzeszow
- koszalin
+ - tomaszowmazowieckiprojekt
+ - rzeszowprojekt
- rawamazowiecka
- zdunskawola
- sieradz
diff --git a/app/src/main/res/values/api_symbols.xml b/app/src/main/res/values/api_symbols.xml
index 6f9b1739d..4b61db48d 100644
--- a/app/src/main/res/values/api_symbols.xml
+++ b/app/src/main/res/values/api_symbols.xml
@@ -4,13 +4,11 @@
- Andrychów
- Augustów
- Baranów Sandomierski
- - Bartoszyce
- Będzin
- Bełchatów
- Bełżyce
- Biała Podlaska
- Biała Rawska
- - Białogard
- Biały Bór
- Białystok
- Biecz
@@ -18,11 +16,9 @@
- Bielsko-Biała
- Bierawa
- Bierutów
- - Biskupice
- Blachownia
- Błaszki
- Błonie
- - Bochnia
- Bogatynia
- Boguchwała
- Boguty-Pianki
@@ -45,6 +41,7 @@
- Chełmża
- Chocianów
- Chodzież
+ - Chojnice
- Chojnów
- Chorzów
- Ciechanów
@@ -58,7 +55,9 @@
- Dąbrowa Górnicza
- Dąbrowa Tarnowska
- Dębica
+ - Dębno
- Dobrzeń Wielki
+ - Dobrzeń Wielki 2
- Dobrzyń Nad Wisłą
- Dolnośląskie
- Duszniki-Zdrój
@@ -76,6 +75,9 @@
- Głogów Małopolski
- Głowno
- Głubczyce
+ - Głubczyce 2
+ - Głuchołazy
+ - Gmina Abramów
- Gmina Adamówka
- Gmina Aleksandrów Kujawski
- Gmina Aleksandrów Łódzki
@@ -87,14 +89,17 @@
- Gmina Bądkowo
- Gmina Bałtów
- Gmina Baranów
- - Gmina Barciany
- Gmina Barcin
+ - Gmina Barczewo
- Gmina Baruchowo
+ - Gmina Batorz
- Gmina Będzino
- Gmina Bełchatów
+ - Gmina Besko
- Gmina Białaczów
- Gmina Białe Błota
- - Gmina Bielsk Podlaski
+ - Gmina Białopole
+ - Gmina Bielsk
- Gmina Bircza
- Gmina Błażowa
- Gmina Błędów
@@ -104,7 +109,6 @@
- Gmina Bobrowniki
- Gmina Bodzentyn
- Gmina Bogoria
- - Gmina Bojanów
- Gmina Bojanowo
- Gmina Bojszowy
- Gmina Bolesławiec
@@ -113,10 +117,14 @@
- Gmina Borów
- Gmina Borowa
- Gmina Borzęcin
+ - Gmina Borzytuchom
+ - Gmina Bralin
- Gmina Branice
- Gmina Braniewo
+ - Gmina Brańszczyk
- Gmina Brąszewice
- Gmina Brenna
+ - Gmina Brok
- Gmina Brzeg Dolny
- Gmina Brzeziny
- Gmina Brzeźnio
@@ -125,13 +133,14 @@
- Gmina Brzuze
- Gmina Brzyska
- Gmina Buczek
- - Gmina Buczkowice
- Gmina Budzów
- Gmina Budzyń
+ - Gmina Bukowina Tatrzańska
- Gmina Bukowsko
- Gmina Byczyna
- Gmina Bystra-Sidzina
- - Gmina Bytoń
+ - Gmina Cegłów
+ - Gmina Cekcyn
- Gmina Ceków-Kolonia
- Gmina Celestynów
- Gmina Cewice
@@ -139,16 +148,25 @@
- Gmina Chełm
- Gmina Chełmiec
- Gmina Chełmno
+ - Gmina Chłopice
- Gmina Chmielnik
+ - Gmina Chociwel
- Gmina Chocz
- Gmina Chodel
+ - Gmina Chodów
- Gmina Chojnice
- Gmina Chojnów
+ - Gmina Chotcza
+ - Gmina Chrząstowice
- Gmina Chrzypsko Wielkie
- Gmina Chybie
- Gmina Ciasna
+ - Gmina Ciechanów
- Gmina Ciechocin
+ - Gmina Cielądz
+ - Gmina Cieszanów
- Gmina Ciężkowice
+ - Gmina Cisek
- Gmina Cisna
- Gmina Cmolas
- Gmina Cyców
@@ -156,19 +174,26 @@
- Gmina Czarna
- Gmina Czarnków
- Gmina Czarny Dunajec
+ - Gmina Czastary
- Gmina Czechowice-Dziedzice
- Gmina Czernichów
- Gmina Czerniejewo
+ - Gmina Czerniewice
+ - Gmina Czernikowo
- Gmina Czerwionka-Leszczyny
- - Gmina Czerwonka
+ - Gmina Czerwonak
- Gmina Człuchów
- Gmina Czosnów
+ - Gmina Dąbrowa Zielona
+ - Gmina Dąbrowice
- Gmina Damasławek
- Gmina Damnica
- Gmina Darłowo
- Gmina Dębe Wielkie
- Gmina Dębica
- Gmina Dębno
+ - Gmina Dębowa Kłoda
+ - Gmina Debrzno
- Gmina Dłutów
- Gmina Dobczyce
- Gmina Dobra
@@ -176,19 +201,31 @@
- Gmina Dobrodzień
- Gmina Dobroń
- Gmina Dobrzany
+ - Gmina Dobrzyca
- Gmina Dobrzyniewo Duże
+ - Gmina Dolsk
- Gmina Dominowo
- Gmina Dorohusk
+ - Gmina Doruchów
+ - Gmina Dragacz
+ - Gmina Drawsko
+ - Gmina Drużbice
- Gmina Drzewica
- Gmina Dubiecko
+ - Gmina Dubienka
- Gmina Dukla
- Gmina Dwikozy
+ - Gmina Dydnia
- Gmina Dynów
- Gmina Dziadowa Kłoda
- Gmina Działoszyce
+ - Gmina Dziemiany
- Gmina Dzierżoniów
+ - Gmina Dzwola
- Gmina Elbląg
- - Gmina Fajsławice
+ - Gmina Ełk
+ - Gmina Fredropol
+ - Gmina Garbatka-Letnisko
- Gmina Garbów
- Gmina Garwolin
- Gmina Gąsawa
@@ -198,29 +235,37 @@
- Gmina Gdów
- Gmina Gielniów
- Gmina Gierałtowice
- - Gmina Glinojeck
- Gmina Głogów
+ - Gmina Głogówek
- Gmina Głuchów
+ - Gmina Głusk
- Gmina Głuszyca
- Gmina Gniew
+ - Gmina Gniewino
- Gmina Gniewoszów
- Gmina Gniezno
- Gmina Goczałkowice-Zdrój
- Gmina Godkowo
- Gmina Godów
- Gmina Godziesze Wielkie
+ - Gmina Godziszów
- Gmina Gołańcz
+ - Gmina Gołcza
- Gmina Goleszów
- Gmina Golina
- Gmina Golub-Dobrzyń
+ - Gmina Gołuchów
+ - Gmina Gomunice
- Gmina Goraj
- Gmina Gorlice
- Gmina Górno
+ - Gmina Górzyca
- Gmina Gościeradów
- Gmina Gostyń
- Gmina Gostynin
- Gmina Goszczyn
- Gmina Gózd
+ - Gmina Grabica
- Gmina Grabów
- Gmina Grabowiec
- Gmina Grabów Nad Pilicą
@@ -239,45 +284,52 @@
- Gmina Grudziądz
- Gmina Gruta
- Gmina Grybów
+ - Gmina Gryfice
+ - Gmina Grzmiąca
- Gmina Haczów
- Gmina Halinów
- Gmina Hańsk
- Gmina Harasiuki
- Gmina Hażlach
- Gmina Herby
+ - Gmina Horodło
- Gmina Hrubieszów
- Gmina Huszlew
- Gmina Hyżne
- Gmina Imielno
- Gmina Inowrocław
+ - Gmina Irządze
- Gmina Istebna
+ - Gmina Iwanowice
- Gmina Iwierzyce
- Gmina Iwonicz-Zdrój
- Gmina Izabelin
- Gmina Izbica
- - Gmina Jadów
+ - Gmina Izbicko
+ - Gmina Jabłoń
- Gmina Jaktorów
+ - Gmina Jakubów
- Gmina Janikowo
+ - Gmina Janów
- Gmina Janowiec
- Gmina Janów Podlaski
- - Gmina Jaraczewo
+ - Gmina Jarczów
- Gmina Jarocin
- Gmina Jasienica Rosielna
+ - Gmina Jaśliska
- Gmina Jasło
- Gmina Jastków
- Gmina Jastrowie
- Gmina Jastrząb
- Gmina Jedlicze
- - Gmina Jedlińsk
- - Gmina Jedlnia-Letnisko
- Gmina Jejkowice
- Gmina Jemielnica
- - Gmina Jemielno
- Gmina Jerzmanowa
- Gmina Jeżewo
- Gmina Jeziora Wielkie
- Gmina Jeziorzany
- Gmina Jeżowe
+ - Gmina Joniec
- Gmina Jordanów
- Gmina Józefów
- Gmina Józefów Nad Wisłą
@@ -285,14 +337,18 @@
- Gmina Kąkolewnica
- Gmina Kamień
- Gmina Kamienica
- - Gmina Kamieniec
+ - Gmina Kamiennik
- Gmina Kamionka
- Gmina Karczmiska
- Gmina Kargowa
+ - Gmina Karlino
+ - Gmina Karniewo
- Gmina Kawęczyn
- Gmina Kazimierz Biskupi
- Gmina Kępice
+ - Gmina Kęsowo
- Gmina Kiełczygłów
+ - Gmina Kietrz
- Gmina Kikół
- Gmina Kiszkowo
- Gmina Kleczew
@@ -307,22 +363,30 @@
- Gmina Klucze
- Gmina Kluczewsko
- Gmina Kobiele Wielkie
+ - Gmina Kobylanka
- Gmina Kochanowice
- Gmina Kock
- Gmina Kodrąb
- Gmina Kołaczyce
- Gmina Kołbaskowo
+ - Gmina Kołbiel
- Gmina Kołczygłowy
+ - Gmina Kołobrzeg
- Gmina Koluszki
- Gmina Komańcza
+ - Gmina Komarówka Podlaska
- Gmina Komorniki
- Gmina Komprachcice
- Gmina Konarzyny
- Gmina Kondratowice
+ - Gmina Koneck
- Gmina Koniusza
- Gmina Konopiska
- Gmina Końskowola
+ - Gmina Konstantynów
- Gmina Koprzywnica
+ - Gmina Korfantów
+ - Gmina Kórnik
- Gmina Korsze
- Gmina Korycin
- Gmina Korzenna
@@ -332,10 +396,14 @@
- Gmina Kościerzyna
- Gmina Kosów Lacki
- Gmina Kostrzyn
- - Gmina Koszyce
+ - Gmina Koszęcin
- Gmina Kotla
- Gmina Kotuń
+ - Gmina Kowiesy
+ - Gmina Koziegłowy
- Gmina Kozłów
+ - Gmina Kramsk
+ - Gmina Kraśniczyn
- Gmina Kraśnik
- Gmina Krasnobród
- Gmina Krasnystaw
@@ -345,12 +413,16 @@
- Gmina Krośnice
- Gmina Krupski Młyn
- Gmina Kruszwica
+ - Gmina Krynice
- Gmina Krynki
- Gmina Krzanowice
- Gmina Krzemieniewo
+ - Gmina Krzeszów
- Gmina Krzymów
+ - Gmina Krzywcza
- Gmina Krzywiń
- Gmina Krzyżanowice
+ - Gmina Ksawerów
- Gmina Książ Wielki
- Gmina Kunice
- Gmina Kunów
@@ -360,15 +432,19 @@
- Gmina Kwilcz
- Gmina Łabowa
- Gmina Łabunie
+ - Gmina Łączna
+ - Gmina Lądek
- Gmina Łambinowice
- Gmina Lanckorona
+ - Gmina Łańcut
+ - Gmina Łapanów
- Gmina Łapsze Niżne
- Gmina Łasin
- Gmina Łaskarzew
- Gmina Lasowice Wielkie
- Gmina Łaszczów
- - Gmina Laszki
- Gmina Latowicz
+ - Gmina Łaziska
- Gmina Łazy
- Gmina Łęczyca
- Gmina Łęczyce
@@ -378,11 +454,14 @@
- Gmina Lelów
- Gmina Leśna
- Gmina Leśna Podlaska
+ - Gmina Leśniowice
- Gmina Lesznowola
- Gmina Leżajsk
- Gmina Lichnowy
- Gmina Limanowa
- Gmina Linia
+ - Gmina Liniewo
+ - Gmina Lipiany
- Gmina Lipinki
- Gmina Lipnik
- Gmina Lipowa
@@ -390,43 +469,51 @@
- Gmina Liszki
- Gmina Liw
- Gmina Łobez
+ - Gmina Łochów
- Gmina Łodygowice
- Gmina Łomazy
+ - Gmina Łomianki
+ - Gmina Łoniów
- Gmina Łopiennik Górny
- Gmina Łopuszno
+ - Gmina Łosice
- Gmina Lubań
- Gmina Lubartów
- Gmina Lubasz
+ - Gmina Lubawka
- Gmina Lubenia
- Gmina Łubianka
- Gmina Lubicz
+ - Gmina Lubień
- Gmina Lubiewo
- Gmina Lubin
- Gmina Łubniany
- Gmina Lubochnia
- - Gmina Lubomia
- Gmina Luboń
+ - Gmina Lubsza
+ - Gmina Lubycza Królewska
- Gmina Łuków
- Gmina Łukowica
- Gmina Lutowiska
+ - Gmina Lututów
- Gmina Luzino
- Gmina Łużna
- Gmina Łysomice
+ - Gmina Maciejowice
- Gmina Magnuszew
+ - Gmina Majdan Królewski
- Gmina Maków Podhalański
- - Gmina Mała Wieś
- Gmina Malbork
- Gmina Małdyty
- Gmina Małkinia Górna
- Gmina Marcinowice
- Gmina Margonin
- Gmina Marianowo
- - Gmina Markusy
- - Gmina Masłów
+ - Gmina Markuszów
+ - Gmina Męcinka
- Gmina Medyka
- Gmina Mełgiew
- Gmina Michałów
- - Gmina Michałowo
- Gmina Miedziana Góra
- Gmina Miedźna
- Gmina Miedźno
@@ -435,7 +522,10 @@
- Gmina Międzyrzec Podlaski
- Gmina Międzyzdroje
- Gmina Miejsce Piastowe
+ - Gmina Miękinia
- Gmina Mielec
+ - Gmina Mielno
+ - Gmina Mieszkowice
- Gmina Milanów
- Gmina Milejów
- Gmina Milicz
@@ -444,21 +534,26 @@
- Gmina Miłosław
- Gmina Milówka
- Gmina Mińsk Mazowiecki
+ - Gmina Mirów
- Gmina Mirsk
- Gmina Młynary
+ - Gmina Modliborzyce
- Gmina Mogielnica
- Gmina Mogilany
+ - Gmina Mogilno
+ - Gmina Morawica
- Gmina Mordy
- Gmina Moryń
- Gmina Mrocza
- Gmina Mrozy
+ - Gmina Mściwojów
+ - Gmina Mstów
- Gmina Mszana
- Gmina Mszana Dolna
- Gmina Murów
- Gmina Mycielin
- - Gmina Mysłakowice
+ - Gmina Mykanów
- Gmina Myślibórz
- - Gmina Nadarzyn
- Gmina Namysłów
- Gmina Nasielsk
- Gmina Nawojowa
@@ -469,23 +564,25 @@
- Gmina Niedrzwica Duża
- Gmina Niedźwiada
- Gmina Niedźwiedź
- - Gmina Niegosławice
- - Gmina Niwiska
+ - Gmina Nowa Karczma
- Gmina Nowa Ruda
- Gmina Nowa Wieś Lęborska
+ - Gmina Nowe
- Gmina Nowe Miasto
- Gmina Nowe Miasto Nad Wartą
- - Gmina Nowogród Bobrzański
+ - Gmina Nowogród
- Gmina Nowosolna
- Gmina Nowy Kawęczyn
+ - Gmina Nowy Korczyn
- Gmina Nowy Staw
- Gmina Nowy Targ
- Gmina Nowy Tomyśl
+ - Gmina Nozdrzec
- Gmina Nur
- Gmina Obrazów
- Gmina Ochotnica Dolna
- Gmina Ogrodzieniec
- - Gmina Olecko
+ - Gmina Olszanica
- Gmina Olsztynek
- Gmina Olszyna
- Gmina Opatowiec
@@ -495,11 +592,15 @@
- Gmina Osiek Jasielski
- Gmina Osiek Mały
- Gmina Osielsko
+ - Gmina Osina
+ - Gmina Osjaków
+ - Gmina Ostroróg
- Gmina Ostrów
- Gmina Ostrówek
- Gmina Ostrów Lubelski
- Gmina Ostrów Mazowiecka
- Gmina Ostrów Wielkopolski
+ - Gmina Otmuchów
- Gmina Otyń
- Gmina Ożarów
- Gmina Ożarowice
@@ -507,27 +608,32 @@
- Gmina Ozorków
- Gmina Pabianice
- Gmina Pacanów
+ - Gmina Pacyna
- Gmina Paczków
- Gmina Padew Narodowa
- - Gmina Pajęczno
- Gmina Pakosław
- Gmina Pakosławice
- Gmina Pałecznica
- Gmina Panki
- Gmina Parchowo
- Gmina Parczew
- - Gmina Pawłosiów
+ - Gmina Pasłęk
+ - Gmina Pątnów
- Gmina Pawłowice
- Gmina Pawłowiczki
+ - Gmina Pawonków
- Gmina Pęcław
- Gmina Pelplin
+ - Gmina Pępowo
- Gmina Piaski
- Gmina Piątnica
- - Gmina Piecki
- Gmina Piekoszów
+ - Gmina Pieniężno
- Gmina Pilchowice
+ - Gmina Pińczów
- Gmina Pionki
- - Gmina Piotrków Trybunalski
+ - Gmina Płaska
+ - Gmina Platerówka
- Gmina Pleśna
- Gmina Pleszew
- Gmina Płońsk
@@ -535,6 +641,7 @@
- Gmina Poczesna
- Gmina Podedwórze
- Gmina Podegrodzie
+ - Gmina Podgórzyn
- Gmina Pokój
- Gmina Połajewo
- Gmina Połaniec
@@ -543,16 +650,18 @@
- Gmina Police
- Gmina Polkowice
- Gmina Pomiechówek
+ - Gmina Poniatowa
- Gmina Popielów
- Gmina Popów
- - Gmina Poraj
- Gmina Potęgowo
+ - Gmina Potok Wielki
- Gmina Praszka
- - Gmina Prażmów
- Gmina Prochowice
- Gmina Promna
- Gmina Prószków
+ - Gmina Prusice
- Gmina Pruszcz Gdański
+ - Gmina Przechlewo
- Gmina Przecław
- Gmina Przedecz
- Gmina Przemęt
@@ -562,7 +671,9 @@
- Gmina Przodkowo
- Gmina Przykona
- Gmina Przyłęk
+ - Gmina Przyrów
- Gmina Przystajń
+ - Gmina Przytoczna
- Gmina Puchaczów
- Gmina Puck
- Gmina Puławy
@@ -570,10 +681,10 @@
- Gmina Puszcza Mariańska
- Gmina Pysznica
- Gmina Pyzdry
+ - Gmina Raba Wyżna
- Gmina Rachanie
- Gmina Raciechowice
- - Gmina Racławice
- - Gmina Radecznica
+ - Gmina Radgoszcz
- Gmina Radków
- Gmina Radłów
- Gmina Radomin
@@ -581,16 +692,29 @@
- Gmina Radomyśl Nad Sanem
- Gmina Radoszyce
- Gmina Radwanice
+ - Gmina Radymno
+ - Gmina Radziejów
- Gmina Radziłów
+ - Gmina Rajgród
+ - Gmina Raków
+ - Gmina Rakszawa
- Gmina Rawa Mazowiecka
+ - Gmina Regnów
- Gmina Reńska Wieś
+ - Gmina Rogóźno
+ - Gmina Rokitno
+ - Gmina Ropa
- Gmina Rossosz
- Gmina Rozprza
- Gmina Ruciane-Nida
- Gmina Ruda-Huta
- Gmina Rudna
- Gmina Rudniki
+ - Gmina Rudnik Nad Sanem
+ - Gmina Rudziniec
- Gmina Rusiec
+ - Gmina Rusinów
+ - Gmina Rybczewice
- Gmina Rychliki
- Gmina Rychtal
- Gmina Ryczywół
@@ -598,32 +722,39 @@
- Gmina Rypin
- Gmina Rytro
- Gmina Rytwiany
- - Gmina Rząśnia
- Gmina Rzeczyca
- Gmina Rzepiennik Strzyżewski
- Gmina Rzepin
+ - Gmina Rzezawa
- Gmina Rzgów
- Gmina Sadki
- Gmina Sadowne
- Gmina Samborzec
- Gmina Sanok
+ - Gmina Sawin
- Gmina Ścinawa
- Gmina Sędziejowice
+ - Gmina Sejny
+ - Gmina Sękowa
- Gmina Sępopol
- Gmina Serokomla
- Gmina Sianów
- Gmina Sicienko
- Gmina Sieciechów
- Gmina Siedlce
+ - Gmina Siedliszcze
- Gmina Siemiatycze
+ - Gmina Siemień
- Gmina Siemyśl
- Gmina Siennica
- Gmina Siennica Różana
- Gmina Sienno
- Gmina Siepraw
- Gmina Sieradz
+ - Gmina Sieraków
- Gmina Sierakowice
- Gmina Siewierz
+ - Gmina Sitkówka-Nowiny
- Gmina Sitno
- Gmina Skarżysko Kościelne
- Gmina Skępe
@@ -631,16 +762,22 @@
- Gmina Skoczów
- Gmina Skoki
- Gmina Skołyszyn
+ - Gmina Skrwilno
- Gmina Skrzyszów
- Gmina Skulsk
+ - Gmina Skwierzyna
- Gmina Sława
+ - Gmina Śliwice
- Gmina Słopnice
+ - Gmina Słubice
- Gmina Słupca
- Gmina Słupia
+ - Gmina Słupia (Konecka)
+ - Gmina Śmigiel
- Gmina Sobienie-Jeziory
+ - Gmina Sobolew
- Gmina Sobótka
- Gmina Sokółka
- - Gmina Sokoły
- Gmina Solina
- Gmina Sośnicowice
- Gmina Sośnie
@@ -655,11 +792,16 @@
- Gmina Stare Miasto
- Gmina Stare Pole
- Gmina Starogard Gdański
+ - Gmina Stary Brus
+ - Gmina Stary Dzierzgoń
+ - Gmina Stary Targ
- Gmina Stawiszyn
+ - Gmina Stepnica
- Gmina Stoczek Łukowski
- Gmina Stopnica
- Gmina Strawczyn
- Gmina Stryków
+ - Gmina Stryszawa
- Gmina Stryszów
- Gmina Strzałkowo
- Gmina Strzelce Opolskie
@@ -668,38 +810,43 @@
- Gmina Strzyżewice
- Gmina Stupsk
- Gmina Subkowy
+ - Gmina Suchań
- Gmina Suchedniów
- Gmina Suchożebry
- Gmina Suchy Las
- Gmina Sulechów
- Gmina Sulęcin
+ - Gmina Sulejów
- Gmina Sulików
- Gmina Sulmierzyce
- Gmina Sułów
- Gmina Susiec
- - Gmina Świerklaniec
+ - Gmina Świercze
+ - Gmina Świerczów
+ - Gmina Świerklany
- Gmina Świerzawa
- Gmina Świeszyno
- Gmina Świlcza
- Gmina Szadek
- Gmina Szaflary
- Gmina Szastarka
+ - Gmina Szczawin Kościelny
- Gmina Szczebrzeszyn
- Gmina Szczekociny
- Gmina Szczerców
- - Gmina Szczutowo
- Gmina Szczytna
- Gmina Szczytniki
- - Gmina Szemud
+ - Gmina Szczytno
- Gmina Szerzyny
- Gmina Szlichtyngowa
+ - Gmina Szreńsk
+ - Gmina Szudziałowo
- Gmina Szydłów
- Gmina Tarłów
- Gmina Tarnów
- Gmina Tarnowiec
- Gmina Tarnów Opolski
- Gmina Teresin
- - Gmina Terespol
- Gmina Tereszpol
- Gmina Tłuchowo
- Gmina Tłuszcz
@@ -709,12 +856,15 @@
- Gmina Toszek
- Gmina Trąbki Wielkie
- Gmina Trzebiatów
+ - Gmina Trzebielino
- Gmina Trzebinia
- - Gmina Trzeszczany
- Gmina Trzyciąż
+ - Gmina Trzydnik Duży
- Gmina Tuchów
+ - Gmina Tułowice
- Gmina Turośń Kościelna
- Gmina Tuszów Narodowy
+ - Gmina Tworóg
- Gmina Tyczyn
- Gmina Tymbark
- Gmina Tyrawa Wołoska
@@ -723,15 +873,21 @@
- Gmina Ulan-Majorat
- Gmina Ulanów
- Gmina Ułęż
+ - Gmina Ulhówek
- Gmina Urszulin
- Gmina Urzędów
+ - Gmina Uście Gorlickie
- Gmina Uścimów
- Gmina Wąchock
+ - Gmina Wądroże Wielkie
- Gmina Wągrowiec
+ - Gmina Walce
- Gmina Wąpielsk
- Gmina Wasilków
+ - Gmina Wąsosz
- Gmina Wąwolnica
- Gmina Wejherowo
+ - Gmina Werbkowice
- Gmina Wiązów
- Gmina Wiązowna
- Gmina Wicko
@@ -739,16 +895,21 @@
- Gmina Wielbark
- Gmina Wieleń
- Gmina Wielgie
+ - Gmina Wielgomłyny
- Gmina Wieliszew
- Gmina Wielka Nieszawka
- Gmina Wieniawa
- Gmina Wieprz
- Gmina Wieruszów
+ - Gmina Wierzbinek
- Gmina Wierzbno
+ - Gmina Wierzchlas
- Gmina Wierzchosławice
- Gmina Wietrzychowice
- Gmina Wijewo
+ - Gmina Wilczyce
- Gmina Wilczyn
+ - Gmina Wilkołaz
- Gmina Wilków
- Gmina Wilkowice
- Gmina Winnica
@@ -759,12 +920,14 @@
- Gmina Witkowo
- Gmina Władysławów
- Gmina Wleń
+ - Gmina Włocławek
- Gmina Włodawa
- Gmina Włoszczowa
- Gmina Wodzierady
- Gmina Wodzisław
- Gmina Wojcieszków
- Gmina Wojnicz
+ - Gmina Wojsławice
- Gmina Wola Krzysztoporska
- Gmina Wolanów
- Gmina Wolbrom
@@ -775,15 +938,18 @@
- Gmina Wręczyca Wielka
- Gmina Wronki
- Gmina Wyrzysk
- - Gmina Zabierzów
+ - Gmina Wysokie
- Gmina Żabno
- Gmina Żagań
- - Gmina Zagórz
+ - Gmina Zagórów
- Gmina Zaklików
- Gmina Zakroczym
- Gmina Zakrzówek
+ - Gmina Zalesie
- Gmina Zaleszany
+ - Gmina Załuski
- Gmina Zamość
+ - Gmina Żarnów
- Gmina Żarnowiec
- Gmina Żarów
- Gmina Zarszyn
@@ -795,20 +961,26 @@
- Gmina Zbójno
- Gmina Zbrosławice
- Gmina Zduńska Wola
- - Gmina Zduny
- Gmina Zdzieszowice
+ - Gmina Zębowice
- Gmina Zebrzydowice
+ - Gmina Żegocina
- Gmina Żelazków
+ - Gmina Zembrzyce
- Gmina Zgierz
- Gmina Zgorzelec
- Gmina Ziębice
- Gmina Zielonki
- Gmina Zławieś Wielka
+ - Gmina Złota
+ - Gmina Złotniki Kujawskie
- Gmina Żmudź
- Gmina Żnin
- Gmina Żółkiewka
- Gmina Żołynia
- Gmina Żukowice
+ - Gmina Żurawica
+ - Gmina Żyraków
- Gmina Żyrzyn
- Gmina Żytno
- Gniezno
@@ -817,19 +989,19 @@
- Góra
- Góra Kalwaria
- Gorlice
+ - Górzno
- Gorzów Śląski
- Gorzów Wielkopolski
- Gostynin
- Grajewo
- Grodzisk Mazowiecki
- - Gronowo Elbląskie
- Grudziądz
+ - Grybów
- Gryfino
- Gryfów Śląski
- Hel
- Hrubieszów
- Inowrocław
- - Iwanowice
- Izbica Kujawska
- Jabłonowo Pomorskie
- Janowiec Wielkopolski
@@ -839,7 +1011,6 @@
- Jasło
- Jastrzębie-Zdrój
- Jawor
- - Jaworzno
- Jedlina-Zdrój
- Jelcz-Laskowice
- Jelenia Góra
@@ -860,9 +1031,9 @@
- Kępno
- Kętrzyn
- Kielce
- - Kiełczygłów
- Kłodawa
- Kłodzko
+ - Kluczbork
- Knurów
- Kobyłka
- Koło
@@ -892,11 +1063,13 @@
- Krzeszowice
- Krzyż Wielkopolski
- Książ Wielkopolski
- - Kudowa-Zdrój
- Kujawsko-Pomorskie
+ - Kutno
- Kuźnia Raciborska
+ - Kwidzyn
- Łabiszyn
- Lądek-Zdrój
+ - Łańcut
- Łapy
- Łask
- Łaskarzew
@@ -908,10 +1081,10 @@
- Legnica
- Leszno
- Lewin Brzeski
+ - Lewin Brzeski 2
- Leżajsk
- Limanowa
- Lipno
- - Lipsko
- Łódź
- Łódzkie
- Łowicz
@@ -928,11 +1101,8 @@
- Lwówek Śląski
- Malbork
- Małopolskie
- - Marciszów
- Marki
- - Masłowice
- Mazowieckie
- - Miastko
- Michałowice
- Miechów
- Międzyrzec Podlaski
@@ -940,6 +1110,7 @@
- Mielec
- Milanówek
- Mińsk Mazowiecki
+ - Mniszków
- Mosina
- Mrągowo
- Mrągowski
@@ -949,18 +1120,17 @@
- Mysłowice
- Myszków
- Nakło Nad Notecią
- - Nasielsk
- Niemodlin
- Niepołomice
- Nisko
- Nowa Dęba
- Nowa Sarzyna
+ - Nowa Sól
- Nowe Miasteczko
- Nowe Skalmierzyce
- Nowogard
- Nowogród Bobrzański
- Nowogrodziec
- - Nowosolna
- Nowy Dwór Mazowiecki
- Nowy Sącz
- Nowy Targ
@@ -974,6 +1144,8 @@
- Opoczno
- Opole
- Opole Lubelskie
+ - Opolskie
+ - Orzesze
- Osieczna
- Osiecznica
- Ostróda
@@ -991,13 +1163,16 @@
- Piekary Śląskie
- Pieńsk
- Piła
+ - Pilzno
- Piotrków Trybunalski
- Pisz
- Płock
- Płońsk
- Pniewy
+ - Pobiedziska
- Podkarpackie
- Podkowa Leśna
+ - Podlaskie
- Połczyn-Zdrój
- Pomorskie
- Poniec
@@ -1006,7 +1181,7 @@
- Powiat augustowski
- Powiat będziński
- Powiat bełchatowski
- - Powiat białobrzeski
+ - Powiat białostocki
- Powiat bialski
- Powiat bielski
- Powiat bieszczadzki
@@ -1032,6 +1207,7 @@
- Powiat człuchowski
- Powiat dąbrowski
- Powiat dębicki
+ - Powiat drawski
- Powiat działdowski
- Powiat dzierżoniowski
- Powiat elbląski
@@ -1046,10 +1222,11 @@
- Powiat goleniowski
- Powiat golubsko-dobrzyński
- Powiat gorlicki
+ - Powiat górowski
- Powiat gorzowski
- Powiat gostyński
+ - Powiat grajewski
- Powiat grójecki
- - Powiat grudziądzki
- Powiat gryficki
- Powiat gryfiński
- Powiat hajnowski
@@ -1064,6 +1241,7 @@
- Powiat jędrzejowski
- Powiat jeleniogórski
- Powiat kaliski
+ - Powiat kamiennogórski
- Powiat kamieński
- Powiat kartuski
- Powiat kazimierski
@@ -1088,6 +1266,7 @@
- Powiat krośnieński
- Powiat krotoszyński
- Powiat kutnowski
+ - Powiat łańcucki
- Powiat łaski
- Powiat lęborski
- Powiat łęczycki
@@ -1102,6 +1281,7 @@
- Powiat lipski
- Powiat łobeski
- Powiat łódzki wschodni
+ - Powiat łosicki
- Powiat łowicki
- Powiat lubaczowski
- Powiat lubański
@@ -1122,9 +1302,11 @@
- Powiat myślenicki
- Powiat myszkowski
- Powiat nakielski
+ - Powiat namysłowski
- Powiat nidzicki
- Powiat niżański
- Powiat nowodworski
+ - Powiat nowomiejski
- Powiat nowosądecki
- Powiat nowosolski
- Powiat nowotarski
@@ -1160,7 +1342,6 @@
- Powiat proszowicki
- Powiat prudnicki
- Powiat pruszkowski
- - Powiat przasnyski
- Powiat przemyski
- Powiat przeworski
- Powiat przysuski
@@ -1183,6 +1364,7 @@
- Powiat rzeszowski
- Powiat sandomierski
- Powiat sanocki
+ - Powiat sejneński
- Powiat sępoleński
- Powiat siedlecki
- Powiat siemiatycki
@@ -1210,6 +1392,7 @@
- Powiat świdnicki
- Powiat świdwiński
- Powiat świebodziński
+ - Powiat świecki
- Powiat szamotulski
- Powiat szczycieński
- Powiat sztumski
@@ -1228,6 +1411,7 @@
- Powiat wągrowiecki
- Powiat wałecki
- Powiat warszawski zachodni
+ - Powiat węgorzewski
- Powiat węgrowski
- Powiat wejherowski
- Powiat wielicki
@@ -1247,7 +1431,6 @@
- Powiat wyszkowski
- Powiat ząbkowicki
- Powiat żagański
- - Powiat zambrowski
- Powiat zamojski
- Powiat żarski
- Powiat zawierciański
@@ -1258,34 +1441,33 @@
- Powiat złotoryjski
- Powiat złotowski
- Powiat żniński
- - Powiat żuromiński
- Powiat żyrardowski
- Powiat żywiecki
- Poznań
- - Prostki
- Proszowice
- Prudnik
+ - Pruszcz Gdański
- Pruszków
- Przasnysz
- Przemyśl
- Przeworsk
- Przysucha
- Pszczyna
- - Pszów
- Puck
- Puławy
+ - Pułtusk
+ - Puszczykowo
- Pyskowice
- Rabka-Zdrój
- Raciąż
- Racibórz
- - Raciechowice
- Radom
- Radomsko
+ - Radomyśl Wielki
- Radymno
- Radziejów
- Radzionków
- Radzyń Podlaski
- - Raków
- Rawa Mazowiecka
- Rawicz
- Reda
@@ -1299,6 +1481,7 @@
- Rymanów
- Rypin
- Rzeszów
+ - Rzeszów projekt
- Sandomierz
- Sanok
- Sędziszów Małopolski
@@ -1306,7 +1489,6 @@
- Siedlce
- Siemianowice Śląskie
- Siemiatycze
- - Sieniawa
- Sieradz
- Skarżysko-Kamienna
- Skawina
@@ -1325,17 +1507,15 @@
- Środa Śląska
- Środa Wielkopolska
- Starachowice
+ - Stargard
- Starogard Gdański
- Stary Sącz
- Staszów
- Stronie Śląskie
- - Strzegom
- Strzyżów
- - Suchy Las
- Sulejówek
- Sułkowice
- Sulmierzyce
- - Suwalski
- Swarzędz
- Świdnica
- Świdnik
@@ -1358,8 +1538,10 @@
- Terespol
- Tomaszów Lubelski
- Tomaszów Mazowiecki
+ - Tomaszów Mazowiecki projekt
- Toruń
- Trzcianka
+ - Trzcińsko-Zdrój
- Trzebnica
- Trzemeszno
- Tuliszków
@@ -1372,7 +1554,7 @@
- Ustrzyki Dolne
- Wadowice
- Wągrowiec
- - Wałbrzych
+ - Wałcz
- Warmińsko-Mazurskie
- Warszawa
- Wąsosz
@@ -1381,7 +1563,7 @@
- Więcbork
- Wieliczka
- Wielkopolskie
- - Wizna
+ - Wieluń
- Władysławowo
- Włocławek
- Włodawa
@@ -1410,6 +1592,7 @@
- Zielona Góra
- Zielonka
- Złotoryja
+ - Złotów
- Żory
- Zwoleń
- Żyrardów
@@ -1418,13 +1601,11 @@
- andrychow
- augustow
- baranowsandomierski
- - bartoszyce
- bedzin
- belchatow
- belzyce
- bialapodlaska
- bialarawska
- - bialogard
- bialybor
- bialystok
- biecz
@@ -1432,11 +1613,9 @@
- bielskobiala
- bierawa
- bierutow
- - biskupice
- blachownia
- blaszki
- blonie
- - bochnia
- bogatynia
- boguchwala
- bogutypianki
@@ -1459,6 +1638,7 @@
- chelmza
- chocianow
- chodziez
+ - chojnice
- chojnow
- chorzow
- ciechanow
@@ -1472,7 +1652,9 @@
- dabrowagornicza
- dabrowatarnowska
- debica
+ - debno
- dobrzenwielki
+ - dobrzenwielki2
- dobrzynnadwisla
- dolnoslaskie
- dusznikizdroj
@@ -1490,6 +1672,9 @@
- glogowmalopolski
- glowno
- glubczyce
+ - glubczyce2
+ - glucholazy
+ - gminaabramow
- gminaadamowka
- gminaaleksandrowkujawski
- gminaaleksandrowlodzki
@@ -1501,14 +1686,17 @@
- gminabadkowo
- gminabaltow
- gminabaranow
- - gminabarciany
- gminabarcin
+ - gminabarczewo
- gminabaruchowo
+ - gminabatorz
- gminabedzino
- gminabelchatow
+ - gminabesko
- gminabialaczow
- gminabialeblota
- - gminabielskpodlaski
+ - gminabialopole
+ - gminabielsk
- gminabircza
- gminablazowa
- gminabledow
@@ -1518,7 +1706,6 @@
- gminabobrowniki
- gminabodzentyn
- gminabogoria
- - gminabojanow
- gminabojanowo
- gminabojszowy
- gminaboleslawiec
@@ -1527,10 +1714,14 @@
- gminaborow
- gminaborowa
- gminaborzecin
+ - gminaborzytuchom
+ - gminabralin
- gminabranice
- gminabraniewo
+ - gminabranszczyk
- gminabraszewice
- gminabrenna
+ - gminabrok
- gminabrzegdolny
- gminabrzeziny
- gminabrzeznio
@@ -1539,13 +1730,14 @@
- gminabrzuze
- gminabrzyska
- gminabuczek
- - gminabuczkowice
- gminabudzow
- gminabudzyn
+ - gminabukowinatatrzanska
- gminabukowsko
- gminabyczyna
- gminabystrasidzina
- - gminabyton
+ - gminaceglow
+ - gminacekcyn
- gminacekowkolonia
- gminacelestynow
- gminacewice
@@ -1553,16 +1745,25 @@
- gminachelm
- gminachelmiec
- gminachelmno
+ - gminachlopice
- gminachmielnik
+ - gminachociwel
- gminachocz
- gminachodel
+ - gminachodow
- gminachojnice
- gminachojnow
+ - gminachotcza
+ - gminachrzastowice
- gminachrzypskowielkie
- gminachybie
- gminaciasna
+ - gminaciechanow
- gminaciechocin
+ - gminacieladz
+ - gminacieszanow
- gminaciezkowice
+ - gminacisek
- gminacisna
- gminacmolas
- gminacycow
@@ -1570,19 +1771,26 @@
- gminaczarna
- gminaczarnkow
- gminaczarnydunajec
+ - gminaczastary
- gminaczechowicedziedzice
- gminaczernichow
- gminaczerniejewo
+ - gminaczerniewice
+ - gminaczernikowo
- gminaczerwionkaleszczyny
- - gminaczerwonka
+ - gminaczerwonak
- gminaczluchow
- gminaczosnow
+ - gminadabrowazielona
+ - gminadabrowice
- gminadamaslawek
- gminadamnica
- gminadarlowo
- gminadebewielkie
- gminadebica
- gminadebno
+ - gminadebowakloda
+ - gminadebrzno
- gminadlutow
- gminadobczyce
- gminadobra
@@ -1590,19 +1798,31 @@
- gminadobrodzien
- gminadobron
- gminadobrzany
+ - gminadobrzyca
- gminadobrzyniewoduze
+ - gminadolsk
- gminadominowo
- gminadorohusk
+ - gminadoruchow
+ - gminadragacz
+ - gminadrawsko
+ - gminadruzbice
- gminadrzewica
- gminadubiecko
+ - gminadubienka
- gminadukla
- gminadwikozy
+ - gminadydnia
- gminadynow
- gminadziadowakloda
- gminadzialoszyce
+ - gminadziemiany
- gminadzierzoniow
+ - gminadzwola
- gminaelblag
- - gminafajslawice
+ - gminaelk
+ - gminafredropol
+ - gminagarbatkaletnisko
- gminagarbow
- gminagarwolin
- gminagasawa
@@ -1612,29 +1832,37 @@
- gminagdow
- gminagielniow
- gminagieraltowice
- - gminaglinojeck
- gminaglogow
+ - gminaglogowek
- gminagluchow
+ - gminaglusk
- gminagluszyca
- gminagniew
+ - gminagniewino
- gminagniewoszow
- gminagniezno
- gminagoczalkowicezdroj
- gminagodkowo
- gminagodow
- gminagodzieszewielkie
+ - gminagodziszow
- gminagolancz
+ - gminagolcza
- gminagoleszow
- gminagolina
- gminagolubdobrzyn
+ - gminagoluchow
+ - gminagomunice
- gminagoraj
- gminagorlice
- gminagorno
+ - gminagorzyca
- gminagoscieradow
- gminagostyn
- gminagostynin
- gminagoszczyn
- gminagozd
+ - gminagrabica
- gminagrabow
- gminagrabowiec
- gminagrabownadpilica
@@ -1653,45 +1881,52 @@
- gminagrudziadz
- gminagruta
- gminagrybow
+ - gminagryfice
+ - gminagrzmiaca
- gminahaczow
- gminahalinow
- gminahansk
- gminaharasiuki
- gminahazlach
- gminaherby
+ - gminahorodlo
- gminahrubieszow
- gminahuszlew
- gminahyzne
- gminaimielno
- gminainowroclaw
+ - gminairzadze
- gminaistebna
+ - gminaiwanowice
- gminaiwierzyce
- gminaiwoniczzdroj
- gminaizabelin
- gminaizbica
- - gminajadow
+ - gminaizbicko
+ - gminajablon
- gminajaktorow
+ - gminajakubow
- gminajanikowo
+ - gminajanow
- gminajanowiec
- gminajanowpodlaski
- - gminajaraczewo
+ - gminajarczow
- gminajarocin
- gminajasienicarosielna
+ - gminajasliska
- gminajaslo
- gminajastkow
- gminajastrowie
- gminajastrzab
- gminajedlicze
- - gminajedlinsk
- - gminajedlnialetnisko
- gminajejkowice
- gminajemielnica
- - gminajemielno
- gminajerzmanowa
- gminajezewo
- gminajeziorawielkie
- gminajeziorzany
- gminajezowe
+ - gminajoniec
- gminajordanow
- gminajozefow
- gminajozefownadwisla
@@ -1699,14 +1934,18 @@
- gminakakolewnica
- gminakamien
- gminakamienica
- - gminakamieniec
+ - gminakamiennik
- gminakamionka
- gminakarczmiska
- gminakargowa
+ - gminakarlino
+ - gminakarniewo
- gminakaweczyn
- gminakazimierzbiskupi
- gminakepice
+ - gminakesowo
- gminakielczyglow
+ - gminakietrz
- gminakikol
- gminakiszkowo
- gminakleczew
@@ -1721,22 +1960,30 @@
- gminaklucze
- gminakluczewsko
- gminakobielewielkie
+ - gminakobylanka
- gminakochanowice
- gminakock
- gminakodrab
- gminakolaczyce
- gminakolbaskowo
+ - gminakolbiel
- gminakolczyglowy
+ - gminakolobrzeg
- gminakoluszki
- gminakomancza
+ - gminakomarowkapodlaska
- gminakomorniki
- gminakomprachcice
- gminakonarzyny
- gminakondratowice
+ - gminakoneck
- gminakoniusza
- gminakonopiska
- gminakonskowola
+ - gminakonstantynow
- gminakoprzywnica
+ - gminakorfantow
+ - gminakornik
- gminakorsze
- gminakorycin
- gminakorzenna
@@ -1746,10 +1993,14 @@
- gminakoscierzyna
- gminakosowlacki
- gminakostrzyn
- - gminakoszyce
+ - gminakoszecin
- gminakotla
- gminakotun
+ - gminakowiesy
+ - gminakozieglowy
- gminakozlow
+ - gminakramsk
+ - gminakrasniczyn
- gminakrasnik
- gminakrasnobrod
- gminakrasnystaw
@@ -1759,12 +2010,16 @@
- gminakrosnice
- gminakrupskimlyn
- gminakruszwica
+ - gminakrynice
- gminakrynki
- gminakrzanowice
- gminakrzemieniewo
+ - gminakrzeszow
- gminakrzymow
+ - gminakrzywcza
- gminakrzywin
- gminakrzyzanowice
+ - gminaksawerow
- gminaksiazwielki
- gminakunice
- gminakunow
@@ -1774,15 +2029,19 @@
- gminakwilcz
- gminalabowa
- gminalabunie
+ - gminalaczna
+ - gminaladek
- gminalambinowice
- gminalanckorona
+ - gminalancut
+ - gminalapanow
- gminalapszenizne
- gminalasin
- gminalaskarzew
- gminalasowicewielkie
- gminalaszczow
- - gminalaszki
- gminalatowicz
+ - gminalaziska
- gminalazy
- gminaleczyca
- gminaleczyce
@@ -1792,11 +2051,14 @@
- gminalelow
- gminalesna
- gminalesnapodlaska
+ - gminalesniowice
- gminalesznowola
- gminalezajsk
- gminalichnowy
- gminalimanowa
- gminalinia
+ - gminaliniewo
+ - gminalipiany
- gminalipinki
- gminalipnik
- gminalipowa
@@ -1804,43 +2066,51 @@
- gminaliszki
- gminaliw
- gminalobez
+ - gminalochow
- gminalodygowice
- gminalomazy
+ - gminalomianki
+ - gminaloniow
- gminalopiennikgorny
- gminalopuszno
+ - gminalosice
- gminaluban
- gminalubartow
- gminalubasz
+ - gminalubawka
- gminalubenia
- gminalubianka
- gminalubicz
+ - gminalubien
- gminalubiewo
- gminalubin
- gminalubniany
- gminalubochnia
- - gminalubomia
- gminalubon
+ - gminalubsza
+ - gminalubyczakrolewska
- gminalukow
- gminalukowica
- gminalutowiska
+ - gminalututow
- gminaluzino
- gminaluzna
- gminalysomice
+ - gminamaciejowice
- gminamagnuszew
+ - gminamajdankrolewski
- gminamakowpodhalanski
- - gminamalawies
- gminamalbork
- gminamaldyty
- gminamalkiniagorna
- gminamarcinowice
- gminamargonin
- gminamarianowo
- - gminamarkusy
- - gminamaslow
+ - gminamarkuszow
+ - gminamecinka
- gminamedyka
- gminamelgiew
- gminamichalow
- - gminamichalowo
- gminamiedzianagora
- gminamiedzna
- gminamiedzno
@@ -1849,7 +2119,10 @@
- gminamiedzyrzecpodlaski
- gminamiedzyzdroje
- gminamiejscepiastowe
+ - gminamiekinia
- gminamielec
+ - gminamielno
+ - gminamieszkowice
- gminamilanow
- gminamilejow
- gminamilicz
@@ -1858,21 +2131,26 @@
- gminamiloslaw
- gminamilowka
- gminaminskmazowiecki
+ - gminamirow
- gminamirsk
- gminamlynary
+ - gminamodliborzyce
- gminamogielnica
- gminamogilany
+ - gminamogilno
+ - gminamorawica
- gminamordy
- gminamoryn
- gminamrocza
- gminamrozy
+ - gminamsciwojow
+ - gminamstow
- gminamszana
- gminamszanadolna
- gminamurow
- gminamycielin
- - gminamyslakowice
+ - gminamykanow
- gminamysliborz
- - gminanadarzyn
- gminanamyslow
- gminanasielsk
- gminanawojowa
@@ -1883,23 +2161,25 @@
- gminaniedrzwicaduza
- gminaniedzwiada
- gminaniedzwiedz
- - gminaniegoslawice
- - gminaniwiska
+ - gminanowakarczma
- gminanowaruda
- gminanowawiesleborska
+ - gminanowe
- gminanowemiasto
- gminanowemiastonadwarta
- - gminanowogrodbobrzanski
+ - gminanowogrod
- gminanowosolna
- gminanowykaweczyn
+ - gminanowykorczyn
- gminanowystaw
- gminanowytarg
- gminanowytomysl
+ - gminanozdrzec
- gminanur
- gminaobrazow
- gminaochotnicadolna
- gminaogrodzieniec
- - gminaolecko
+ - gminaolszanica
- gminaolsztynek
- gminaolszyna
- gminaopatowiec
@@ -1909,11 +2189,15 @@
- gminaosiekjasielski
- gminaosiekmaly
- gminaosielsko
+ - gminaosina
+ - gminaosjakow
+ - gminaostrorog
- gminaostrow
- gminaostrowek
- gminaostrowlubelski
- gminaostrowmazowiecka
- gminaostrowwielkopolski
+ - gminaotmuchow
- gminaotyn
- gminaozarow
- gminaozarowice
@@ -1921,27 +2205,32 @@
- gminaozorkow
- gminapabianice
- gminapacanow
+ - gminapacyna
- gminapaczkow
- gminapadewnarodowa
- - gminapajeczno
- gminapakoslaw
- gminapakoslawice
- gminapalecznica
- gminapanki
- gminaparchowo
- gminaparczew
- - gminapawlosiow
+ - gminapaslek
+ - gminapatnow
- gminapawlowice
- gminapawlowiczki
+ - gminapawonkow
- gminapeclaw
- gminapelplin
+ - gminapepowo
- gminapiaski
- gminapiatnica
- - gminapiecki
- gminapiekoszow
+ - gminapieniezno
- gminapilchowice
+ - gminapinczow
- gminapionki
- - gminapiotrkowtrybunalski
+ - gminaplaska
+ - gminaplaterowka
- gminaplesna
- gminapleszew
- gminaplonsk
@@ -1949,6 +2238,7 @@
- gminapoczesna
- gminapodedworze
- gminapodegrodzie
+ - gminapodgorzyn
- gminapokoj
- gminapolajewo
- gminapolaniec
@@ -1957,16 +2247,18 @@
- gminapolice
- gminapolkowice
- gminapomiechowek
+ - gminaponiatowa
- gminapopielow
- gminapopow
- - gminaporaj
- gminapotegowo
+ - gminapotokwielki
- gminapraszka
- - gminaprazmow
- gminaprochowice
- gminapromna
- gminaproszkow
+ - gminaprusice
- gminapruszczgdanski
+ - gminaprzechlewo
- gminaprzeclaw
- gminaprzedecz
- gminaprzemet
@@ -1976,7 +2268,9 @@
- gminaprzodkowo
- gminaprzykona
- gminaprzylek
+ - gminaprzyrow
- gminaprzystajn
+ - gminaprzytoczna
- gminapuchaczow
- gminapuck
- gminapulawy
@@ -1984,10 +2278,10 @@
- gminapuszczamarianska
- gminapysznica
- gminapyzdry
+ - gminarabawyzna
- gminarachanie
- gminaraciechowice
- - gminaraclawice
- - gminaradecznica
+ - gminaradgoszcz
- gminaradkow
- gminaradlow
- gminaradomin
@@ -1995,16 +2289,29 @@
- gminaradomyslnadsanem
- gminaradoszyce
- gminaradwanice
+ - gminaradymno
+ - gminaradziejow
- gminaradzilow
+ - gminarajgrod
+ - gminarakow
+ - gminarakszawa
- gminarawamazowiecka
+ - gminaregnow
- gminarenskawies
+ - gminarogozno
+ - gminarokitno
+ - gminaropa
- gminarossosz
- gminarozprza
- gminarucianenida
- gminarudahuta
- gminarudna
- gminarudniki
+ - gminarudniknadsanem
+ - gminarudziniec
- gminarusiec
+ - gminarusinow
+ - gminarybczewice
- gminarychliki
- gminarychtal
- gminaryczywol
@@ -2012,32 +2319,39 @@
- gminarypin
- gminarytro
- gminarytwiany
- - gminarzasnia
- gminarzeczyca
- gminarzepiennikstrzyzewski
- gminarzepin
+ - gminarzezawa
- gminarzgow
- gminasadki
- gminasadowne
- gminasamborzec
- gminasanok
+ - gminasawin
- gminascinawa
- gminasedziejowice
+ - gminasejny
+ - gminasekowa
- gminasepopol
- gminaserokomla
- gminasianow
- gminasicienko
- gminasieciechow
- gminasiedlce
+ - gminasiedliszcze
- gminasiemiatycze
+ - gminasiemien
- gminasiemysl
- gminasiennica
- gminasiennicarozana
- gminasienno
- gminasiepraw
- gminasieradz
+ - gminasierakow
- gminasierakowice
- gminasiewierz
+ - gminasitkowkanowiny
- gminasitno
- gminaskarzyskokoscielne
- gminaskepe
@@ -2045,16 +2359,22 @@
- gminaskoczow
- gminaskoki
- gminaskolyszyn
+ - gminaskrwilno
- gminaskrzyszow
- gminaskulsk
+ - gminaskwierzyna
- gminaslawa
+ - gminasliwice
- gminaslopnice
+ - gminaslubice
- gminaslupca
- gminaslupia
+ - gminaslupiakonecka
+ - gminasmigiel
- gminasobieniejeziory
+ - gminasobolew
- gminasobotka
- gminasokolka
- - gminasokoly
- gminasolina
- gminasosnicowice
- gminasosnie
@@ -2069,11 +2389,16 @@
- gminastaremiasto
- gminastarepole
- gminastarogardgdanski
+ - gminastarybrus
+ - gminastarydzierzgon
+ - gminastarytarg
- gminastawiszyn
+ - gminastepnica
- gminastoczeklukowski
- gminastopnica
- gminastrawczyn
- gminastrykow
+ - gminastryszawa
- gminastryszow
- gminastrzalkowo
- gminastrzelceopolskie
@@ -2082,38 +2407,43 @@
- gminastrzyzewice
- gminastupsk
- gminasubkowy
+ - gminasuchan
- gminasuchedniow
- gminasuchozebry
- gminasuchylas
- gminasulechow
- gminasulecin
+ - gminasulejow
- gminasulikow
- gminasulmierzyce
- gminasulow
- gminasusiec
- - gminaswierklaniec
+ - gminaswiercze
+ - gminaswierczow
+ - gminaswierklany
- gminaswierzawa
- gminaswieszyno
- gminaswilcza
- gminaszadek
- gminaszaflary
- gminaszastarka
+ - gminaszczawinkoscielny
- gminaszczebrzeszyn
- gminaszczekociny
- gminaszczercow
- - gminaszczutowo
- gminaszczytna
- gminaszczytniki
- - gminaszemud
+ - gminaszczytno
- gminaszerzyny
- gminaszlichtyngowa
+ - gminaszrensk
+ - gminaszudzialowo
- gminaszydlow
- gminatarlow
- gminatarnow
- gminatarnowiec
- gminatarnowopolski
- gminateresin
- - gminaterespol
- gminatereszpol
- gminatluchowo
- gminatluszcz
@@ -2123,12 +2453,15 @@
- gminatoszek
- gminatrabkiwielkie
- gminatrzebiatow
+ - gminatrzebielino
- gminatrzebinia
- - gminatrzeszczany
- gminatrzyciaz
+ - gminatrzydnikduzy
- gminatuchow
+ - gminatulowice
- gminaturosnkoscielna
- gminatuszownarodowy
+ - gminatworog
- gminatyczyn
- gminatymbark
- gminatyrawawoloska
@@ -2137,15 +2470,21 @@
- gminaulanmajorat
- gminaulanow
- gminaulez
+ - gminaulhowek
- gminaurszulin
- gminaurzedow
+ - gminausciegorlickie
- gminauscimow
- gminawachock
+ - gminawadrozewielkie
- gminawagrowiec
+ - gminawalce
- gminawapielsk
- gminawasilkow
+ - gminawasosz
- gminawawolnica
- gminawejherowo
+ - gminawerbkowice
- gminawiazow
- gminawiazowna
- gminawicko
@@ -2153,16 +2492,21 @@
- gminawielbark
- gminawielen
- gminawielgie
+ - gminawielgomlyny
- gminawieliszew
- gminawielkanieszawka
- gminawieniawa
- gminawieprz
- gminawieruszow
+ - gminawierzbinek
- gminawierzbno
+ - gminawierzchlas
- gminawierzchoslawice
- gminawietrzychowice
- gminawijewo
+ - gminawilczyce
- gminawilczyn
+ - gminawilkolaz
- gminawilkow
- gminawilkowice
- gminawinnica
@@ -2173,12 +2517,14 @@
- gminawitkowo
- gminawladyslawow
- gminawlen
+ - gminawloclawek
- gminawlodawa
- gminawloszczowa
- gminawodzierady
- gminawodzislaw
- gminawojcieszkow
- gminawojnicz
+ - gminawojslawice
- gminawolakrzysztoporska
- gminawolanow
- gminawolbrom
@@ -2189,15 +2535,18 @@
- gminawreczycawielka
- gminawronki
- gminawyrzysk
- - gminazabierzow
+ - gminawysokie
- gminazabno
- gminazagan
- - gminazagorz
+ - gminazagorow
- gminazaklikow
- gminazakroczym
- gminazakrzowek
+ - gminazalesie
- gminazaleszany
+ - gminazaluski
- gminazamosc
+ - gminazarnow
- gminazarnowiec
- gminazarow
- gminazarszyn
@@ -2209,20 +2558,26 @@
- gminazbojno
- gminazbroslawice
- gminazdunskawola
- - gminazduny
- gminazdzieszowice
+ - gminazebowice
- gminazebrzydowice
+ - gminazegocina
- gminazelazkow
+ - gminazembrzyce
- gminazgierz
- gminazgorzelec
- gminaziebice
- gminazielonki
- gminazlawieswielka
+ - gminazlota
+ - gminazlotnikikujawskie
- gminazmudz
- gminaznin
- gminazolkiewka
- gminazolynia
- gminazukowice
+ - gminazurawica
+ - gminazyrakow
- gminazyrzyn
- gminazytno
- gniezno
@@ -2231,19 +2586,19 @@
- gora
- gorakalwaria
- gorlice
+ - gorzno
- gorzowslaski
- gorzowwielkopolski
- gostynin
- grajewo
- grodziskmazowiecki
- - gronowoelblaskie
- grudziadz
+ - grybow
- gryfino
- gryfowslaski
- hel
- hrubieszow
- inowroclaw
- - iwanowice
- izbicakujawska
- jablonowopomorskie
- janowiecwielkopolski
@@ -2253,7 +2608,6 @@
- jaslo
- jastrzebiezdroj
- jawor
- - jaworzno
- jedlinazdroj
- jelczlaskowice
- jeleniagora
@@ -2274,9 +2628,9 @@
- kepno
- ketrzyn
- kielce
- - kielczyglow
- klodawa
- klodzko
+ - kluczbork
- knurow
- kobylka
- kolo
@@ -2306,11 +2660,13 @@
- krzeszowice
- krzyzwielkopolski
- ksiazwielkopolski
- - kudowazdroj
- kujawskopomorskie
+ - kutno
- kuzniaraciborska
+ - kwidzyn
- labiszyn
- ladekzdroj
+ - lancut
- lapy
- lask
- laskarzew
@@ -2322,10 +2678,10 @@
- legnica
- leszno
- lewinbrzeski
+ - lewinbrzeski2
- lezajsk
- limanowa
- lipno
- - lipsko
- lodz
- lodzkie
- lowicz
@@ -2342,11 +2698,8 @@
- lwowekslaski
- malbork
- malopolskie
- - marciszow
- marki
- - maslowice
- mazowieckie
- - miastko
- michalowice
- miechow
- miedzyrzecpodlaski
@@ -2354,6 +2707,7 @@
- mielec
- milanowek
- minskmazowiecki
+ - mniszkow
- mosina
- mragowo
- mragowski
@@ -2363,18 +2717,17 @@
- myslowice
- myszkow
- naklonadnotecia
- - nasielsk
- niemodlin
- niepolomice
- nisko
- nowadeba
- nowasarzyna
+ - nowasol
- nowemiasteczko
- noweskalmierzyce
- nowogard
- nowogrodbobrzanski
- nowogrodziec
- - nowosolna
- nowydwormazowiecki
- nowysacz
- nowytarg
@@ -2388,6 +2741,8 @@
- opoczno
- opole
- opolelubelskie
+ - opolskie
+ - orzesze
- osieczna
- osiecznica
- ostroda
@@ -2405,13 +2760,16 @@
- piekaryslaskie
- piensk
- pila
+ - pilzno
- piotrkowtrybunalski
- pisz
- plock
- plonsk
- pniewy
+ - pobiedziska
- podkarpackie
- podkowalesna
+ - podlaskie
- polczynzdroj
- pomorskie
- poniec
@@ -2420,7 +2778,7 @@
- powiataugustowski
- powiatbedzinski
- powiatbelchatowski
- - powiatbialobrzeski
+ - powiatbialostocki
- powiatbialski
- powiatbielski
- powiatbieszczadzki
@@ -2446,6 +2804,7 @@
- powiatczluchowski
- powiatdabrowski
- powiatdebicki
+ - powiatdrawski
- powiatdzialdowski
- powiatdzierzoniowski
- powiatelblaski
@@ -2460,10 +2819,11 @@
- powiatgoleniowski
- powiatgolubskodobrzynski
- powiatgorlicki
+ - powiatgorowski
- powiatgorzowski
- powiatgostynski
+ - powiatgrajewski
- powiatgrojecki
- - powiatgrudziadzki
- powiatgryficki
- powiatgryfinski
- powiathajnowski
@@ -2478,6 +2838,7 @@
- powiatjedrzejowski
- powiatjeleniogorski
- powiatkaliski
+ - powiatkamiennogorski
- powiatkamienski
- powiatkartuski
- powiatkazimierski
@@ -2502,6 +2863,7 @@
- powiatkrosnienski
- powiatkrotoszynski
- powiatkutnowski
+ - powiatlancucki
- powiatlaski
- powiatleborski
- powiatleczycki
@@ -2516,6 +2878,7 @@
- powiatlipski
- powiatlobeski
- powiatlodzkiwschodni
+ - powiatlosicki
- powiatlowicki
- powiatlubaczowski
- powiatlubanski
@@ -2536,9 +2899,11 @@
- powiatmyslenicki
- powiatmyszkowski
- powiatnakielski
+ - powiatnamyslowski
- powiatnidzicki
- powiatnizanski
- powiatnowodworski
+ - powiatnowomiejski
- powiatnowosadecki
- powiatnowosolski
- powiatnowotarski
@@ -2574,7 +2939,6 @@
- powiatproszowicki
- powiatprudnicki
- powiatpruszkowski
- - powiatprzasnyski
- powiatprzemyski
- powiatprzeworski
- powiatprzysuski
@@ -2597,6 +2961,7 @@
- powiatrzeszowski
- powiatsandomierski
- powiatsanocki
+ - powiatsejnenski
- powiatsepolenski
- powiatsiedlecki
- powiatsiemiatycki
@@ -2624,6 +2989,7 @@
- powiatswidnicki
- powiatswidwinski
- powiatswiebodzinski
+ - powiatswiecki
- powiatszamotulski
- powiatszczycienski
- powiatsztumski
@@ -2642,6 +3008,7 @@
- powiatwagrowiecki
- powiatwalecki
- powiatwarszawskizachodni
+ - powiatwegorzewski
- powiatwegrowski
- powiatwejherowski
- powiatwielicki
@@ -2661,7 +3028,6 @@
- powiatwyszkowski
- powiatzabkowicki
- powiatzaganski
- - powiatzambrowski
- powiatzamojski
- powiatzarski
- powiatzawiercianski
@@ -2672,34 +3038,33 @@
- powiatzlotoryjski
- powiatzlotowski
- powiatzninski
- - powiatzurominski
- powiatzyrardowski
- powiatzywiecki
- poznan
- - prostki
- proszowice
- prudnik
+ - pruszczgdanski
- pruszkow
- przasnysz
- przemysl
- przeworsk
- przysucha
- pszczyna
- - pszow
- puck
- pulawy
+ - pultusk
+ - puszczykowo
- pyskowice
- rabkazdroj
- raciaz
- raciborz
- - raciechowice
- radom
- radomsko
+ - radomyslwielki
- radymno
- radziejow
- radzionkow
- radzynpodlaski
- - rakow
- rawamazowiecka
- rawicz
- reda
@@ -2713,6 +3078,7 @@
- rymanow
- rypin
- rzeszow
+ - rzeszowprojekt
- sandomierz
- sanok
- sedziszowmalopolski
@@ -2720,7 +3086,6 @@
- siedlce
- siemianowiceslaskie
- siemiatycze
- - sieniawa
- sieradz
- skarzyskokamienna
- skawina
@@ -2739,17 +3104,15 @@
- srodaslaska
- srodawielkopolska
- starachowice
+ - stargard
- starogardgdanski
- starysacz
- staszow
- stronieslaskie
- - strzegom
- strzyzow
- - suchylas
- sulejowek
- sulkowice
- sulmierzyce
- - suwalski
- swarzedz
- swidnica
- swidnik
@@ -2772,8 +3135,10 @@
- terespol
- tomaszowlubelski
- tomaszowmazowiecki
+ - tomaszowmazowieckiprojekt
- torun
- trzcianka
+ - trzcinskozdroj
- trzebnica
- trzemeszno
- tuliszkow
@@ -2786,7 +3151,7 @@
- ustrzykidolne
- wadowice
- wagrowiec
- - walbrzych
+ - walcz
- warminskomazurskie
- warszawa
- wasosz
@@ -2795,7 +3160,7 @@
- wiecbork
- wieliczka
- wielkopolskie
- - wizna
+ - wielun
- wladyslawowo
- wloclawek
- wlodawa
@@ -2824,6 +3189,7 @@
- zielonagora
- zielonka
- zlotoryja
+ - zlotow
- zory
- zwolen
- zyrardow
diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml
index f29080b33..80a71bc71 100644
--- a/app/src/main/res/values/preferences_keys.xml
+++ b/app/src/main/res/values/preferences_keys.xml
@@ -31,8 +31,7 @@
homework_fullscreen
subjects_without_grades
optional_arithmetic_average
- message_send_is_draft
- message_send_recipients
+ message_draft
last_sync_date
notifications_piggyback
notifications_piggyback_cancel_original
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2eee4cce4..38997054f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -26,15 +26,11 @@
Student info
Dashboard
Notifications center
-
-
Semester %1$d, %2$d/%3$d
-
-
Sign in with the student or parent account
- Enter the symbol from the register page for account: <b>%1$s</b>
+ Enter the symbol from the register page for account: <b>%1$s</b>
Username
Email
Login, PESEL or e-mail
@@ -59,7 +55,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
+ The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login 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,15 +67,21 @@
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 i miejscowość:
+ Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nIdentyfikator instalacji: %5$s\nOstatni błąd: %6$s\n\nNazwa szkoły i miejscowość:
Make sure you select the correct UONET+ register variation!
I forgot my password
Recover your account
Recover
Student is already signed in
Standard
-
-
+ Other search locations
+ No active students found
+ Enter a different symbol
+
+ Enable notifications
+ Enable notifications so you don\'t miss message from teacher or new grade
+ Skip
+ Enable
Account manager
Log in
@@ -88,8 +90,6 @@
Application support
Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time
Enable ads
-
-
Grade
Semester %d
@@ -152,8 +152,6 @@
- You received %1$d final grade
- You received %1$d final grades
-
-
Lesson
Room
@@ -189,8 +187,6 @@
- %d change
- %d changes
-
-
Completed lessons
Show completed lessons
@@ -198,8 +194,6 @@
Topic
Absence
Resources
-
-
Additional lessons
Show additional lessons
@@ -215,8 +209,6 @@
Start time
End time
End time must be greater than start time
-
-
Attendance summary
Absent for school reasons
@@ -249,12 +241,8 @@
- %d attendance
- %d attendance
-
-
Total
-
-
No exams this week
Type
@@ -271,8 +259,6 @@
- %d exam
- %d exams
-
-
Inbox
Sent
@@ -301,9 +287,11 @@
Message does not exist
You need to choose at least 1 recipient
The message content must be at least 3 characters
+ All mailboxes
Only unread
Only with attachments
Read: %s
+ Read by: %1$d of %2$d people
- %1$d message
- %1$d messages
@@ -323,8 +311,7 @@
- %1$d selected
Messages deleted
-
-
+ Choose mailbox
No info about notes
Points
@@ -340,7 +327,6 @@
- You received %1$d note
- You received %1$d notes
-
- %d praise
@@ -354,7 +340,6 @@
- You received %1$d praise
- You received %1$d praises
-
- %d neutral note
@@ -368,8 +353,6 @@
- You received %1$d neutral note
- You received %1$d neutral notes
-
-
No info about homework
Mark as done
@@ -390,8 +373,6 @@
- %d homework
- %d homework
-
-
Lucky number
Today\'s lucky number is
@@ -399,13 +380,9 @@
Lucky number for today
Today\'s lucky number is: %s
Show history
-
-
Lucky number history
No info about lucky numbers
-
-
Mobile devices
No devices
@@ -415,12 +392,8 @@
Token
Symbol
PIN
-
-
School and teachers
-
-
School
No info about school
@@ -431,14 +404,10 @@
Name of pedagogue
Show on map
Call
-
-
Teachers
No info about teachers
No subject
-
-
Conferences
No info about conferences
@@ -456,7 +425,8 @@
Present at conference
Agenda
-
+ Place
+ Topic
School announcements
No school announcements
@@ -472,8 +442,6 @@
- You have %1$d new school announcement
- You have %1$d new school announcements
-
-
Add account
Logout
@@ -488,8 +456,6 @@
Contact
Residence details
Personal information
-
-
App version
Contributors
@@ -512,18 +478,12 @@
Visit the website and help develop the application
Licenses
Licenses of libraries used in the application
- Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\n\nTreść zgłoszenia:
-
-
+ Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nIdentyfikator instalacji: %4$s\nTreść zgłoszenia:
License
-
-
Avatar
See more on GitHub
-
-
No info about student or student family
Name
@@ -546,19 +506,13 @@
Female
Last name
Guardian
-
-
Nick
Add nick
Choose avatar color
-
-
Share logs
Refresh
-
-
Lessons
(Tomorrow)
@@ -577,7 +531,6 @@
until %1$s
No upcoming lessons
An error occurred while loading the lessons
-
Homework
No homework to do
An error occurred while loading the homework
@@ -586,11 +539,9 @@
- %1$d more homework
due %1$s
-
Last grades
No new grades
An error occurred while loading the grades
-
School announcements
No current announcements
An error occurred while loading the announcements
@@ -598,7 +549,6 @@
- %1$d more announcement
- %1$d more announcements
-
Exams
No upcoming exams
An error occurred while loading the exams
@@ -606,7 +556,6 @@
- %1$d more exam
- %1$d more exams
-
Conferences
No upcoming conferences
An error occurred while loading the conferences
@@ -614,16 +563,11 @@
- %1$d more conference
- %1$d more conferences
-
An error occurred while loading data
None
-
-
Check for updates
Before reporting a bug, check first if an update with the bug fix is available
-
-
Content
Retry
@@ -651,16 +595,12 @@
Undo
Change
Add to calendar
-
-
No lessons
Choose theme
Light
Dark
System Theme
-
-
App
Default view
@@ -676,7 +616,6 @@
Grades color scheme
Subjects sorting
Language
-
Notifications
Other
Show notifications
@@ -696,7 +635,6 @@
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
-
Synchronization
Automatic update
Suspended on holidays
@@ -707,13 +645,15 @@
Sync failed
Sync in progress
Last full sync: %s
-
Value of the plus
Value of the minus
Reply with message history
Show arithmetic average when no weights provided
-
Support
+ Privacy Policy
+ Agreements
+ Consent to processing of data related to ads
+ Show ads in app
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
@@ -725,13 +665,14 @@
You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details
Personalized ads
Non-personalized ads
-
+ I am over 18 years old
+ Yes, personalized ads
+ Yes, non-personalized ads
Advanced
Appearance & Behavior
Notifications
Synchronization
Advertisements
-
Grades
Dashboard
Tiles visibility
@@ -740,7 +681,6 @@
Grades
Calculated average
Messages
-
Appearance & Behavior
Languages, themes, subjects sorting
App notifications, fix problems
@@ -751,8 +691,6 @@
Advanced
App version, contributors, social portals
Displaying advertisements, project support
-
-
New grades
New homework
@@ -767,8 +705,6 @@
Debug
Timetable change
New attendance
-
-
Black
Red
@@ -776,15 +712,11 @@
Green
Purple
No color
-
-
Download of updates has started…
An update has just been downloaded.
Restart
Update failed! Wulkanowy may not function properly. Consider updating
-
-
No internet connection
An error occurred. Check your device clock
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..418b6d4c4
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
index 84ff05a04..17fac4d1e 100644
--- a/app/src/main/res/xml/network_security_config.xml
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -1,6 +1,5 @@
-
diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt
index 772d616d7..28c98e3c3 100644
--- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt
+++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt
@@ -31,13 +31,22 @@ class AdsPresenter @Inject constructor(
view?.showLoadingSupportAd(true)
presenterScope.launch {
runCatching { adsHelper.getSupportAd() }
- .onFailure(errorHandler::dispatch)
- .onSuccess { it?.let { view?.showAd(it) } }
+ .onFailure {
+ errorHandler.dispatch(it)
- view?.run {
- showLoadingSupportAd(false)
- showWatchAdOncePerVisit(true)
- }
+ view?.run {
+ showLoadingSupportAd(false)
+ showWatchAdOncePerVisit(false)
+ }
+ }
+ .onSuccess {
+ it?.let { view?.showAd(it) }
+
+ view?.run {
+ showLoadingSupportAd(false)
+ showWatchAdOncePerVisit(true)
+ }
+ }
}
}
diff --git a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt
index c536e2218..d5f65b46d 100644
--- a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt
+++ b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt
@@ -1,8 +1,12 @@
package io.github.wulkanowy.utils
import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+import android.os.Build
import android.os.Bundle
import android.view.View
+import androidx.core.content.getSystemService
import com.google.ads.mediation.admob.AdMobAdapter
import com.google.android.gms.ads.*
import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
@@ -10,6 +14,7 @@ import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoa
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.BuildConfig
import io.github.wulkanowy.data.repositories.PreferencesRepository
+import java.net.UnknownHostException
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@@ -28,6 +33,10 @@ class AdsHelper @Inject constructor(
}
suspend fun getSupportAd(): RewardedInterstitialAd? {
+ if (!context.isInternetConnected()) {
+ throw UnknownHostException()
+ }
+
val extra = Bundle().apply { putString("npa", "1") }
val adRequest = AdRequest.Builder()
.apply {
@@ -84,4 +93,18 @@ class AdsHelper @Inject constructor(
}
}
+@Suppress("DEPRECATION")
+private fun Context.isInternetConnected(): Boolean {
+ val connectivityManager = getSystemService() ?: return false
+
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val currentNetwork = connectivityManager.activeNetwork
+ val networkCapabilities = connectivityManager.getNetworkCapabilities(currentNetwork)
+
+ networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == true
+ } else {
+ connectivityManager.activeNetworkInfo?.isConnected == true
+ }
+}
+
data class AdBanner(val view: View)
diff --git a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
index b65325790..3215fa20c 100644
--- a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
+++ b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
@@ -4,25 +4,37 @@ import android.app.Activity
import android.content.Context
import android.os.Bundle
import com.google.firebase.analytics.FirebaseAnalytics
+import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.hilt.android.qualifiers.ApplicationContext
+import io.github.wulkanowy.data.repositories.PreferencesRepository
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AnalyticsHelper @Inject constructor(
- @ApplicationContext private val context: Context
+ @ApplicationContext private val context: Context,
+ preferencesRepository: PreferencesRepository,
+ appInfo: AppInfo,
) {
private val analytics by lazy { FirebaseAnalytics.getInstance(context) }
+ private val crashlytics by lazy { FirebaseCrashlytics.getInstance() }
+
+ init {
+ if (!appInfo.isDebug) {
+ crashlytics.setUserId(preferencesRepository.installationId)
+ }
+ }
+
fun logEvent(name: String, vararg params: Pair) {
Bundle().apply {
- params.forEach {
- if (it.second == null) return@forEach
- when (it.second) {
- is String, is String? -> putString(it.first, it.second.toString())
- is Int, is Int? -> putInt(it.first, it.second as Int)
- is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
+ params.forEach { (key, value) ->
+ if (value == null) return@forEach
+ when (value) {
+ is String -> putString(key, value)
+ is Int -> putInt(key, value)
+ is Boolean -> putBoolean(key, value)
}
}
analytics.logEvent(name, this)
diff --git a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt
index 410fddf16..f980bc4bb 100644
--- a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt
+++ b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt
@@ -23,9 +23,6 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter)
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return
- crashlytics.setCustomKey("priority", priority)
- crashlytics.setCustomKey("tag", tag.orEmpty())
- crashlytics.setCustomKey("message", message)
if (t != null) {
crashlytics.recordException(t)
} else {
diff --git a/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt
new file mode 100644
index 000000000..d17bfb4eb
--- /dev/null
+++ b/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt
@@ -0,0 +1,35 @@
+package io.github.wulkanowy.utils
+
+import android.content.Context
+import com.google.firebase.FirebaseApp
+import com.google.firebase.ktx.Firebase
+import com.google.firebase.remoteconfig.ktx.remoteConfig
+import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class RemoteConfigHelper @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val appInfo: AppInfo,
+) : BaseRemoteConfigHelper() {
+
+ override fun initialize() {
+ FirebaseApp.initializeApp(context)
+
+ Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
+ fetchTimeoutInSeconds = 3
+ if (appInfo.isDebug) {
+ minimumFetchIntervalInSeconds = 0
+ }
+ })
+ Firebase.remoteConfig.setDefaultsAsync(RemoteConfigDefaults.values().associate {
+ it.key to it.value
+ })
+ Firebase.remoteConfig.fetchAndActivate()
+ }
+
+ override val userAgentTemplate: String
+ get() = Firebase.remoteConfig.getString(RemoteConfigDefaults.USER_AGENT_TEMPLATE.key)
+}
diff --git a/app/src/play/res/xml/scheme_preferences_ads.xml b/app/src/play/res/xml/scheme_preferences_ads.xml
index d5e93a355..4165561a7 100644
--- a/app/src/play/res/xml/scheme_preferences_ads.xml
+++ b/app/src/play/res/xml/scheme_preferences_ads.xml
@@ -2,18 +2,18 @@
+ app:title="@string/pref_ads_agreements">
+ app:title="@string/pref_ads_privacy_policy" />
+ app:title="@string/pref_ads_consent" />
+ app:title="@string/pref_ads_show_in_app" />
> {
diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration27Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration27Test.kt
index 8e744f27a..19eda9ba8 100644
--- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration27Test.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration27Test.kt
@@ -27,7 +27,7 @@ class Migration27Test : AbstractMigrationTest() {
close()
}
- helper.runMigrationsAndValidate(dbName, 27, true, Migration27())
+ runMigrationsAndValidate(Migration27())
val db = getMigratedRoomDatabase()
val students = runBlocking { db.studentDao.loadAll() }
@@ -39,6 +39,8 @@ class Migration27Test : AbstractMigrationTest() {
assertEquals(123, userLoginId)
assertEquals("Student Jan", userName)
}
+
+ db.close()
}
@Test
@@ -49,7 +51,7 @@ class Migration27Test : AbstractMigrationTest() {
close()
}
- helper.runMigrationsAndValidate(dbName, 27, true, Migration27())
+ runMigrationsAndValidate(Migration27())
val db = getMigratedRoomDatabase()
val students = runBlocking { db.studentDao.loadAll() }
@@ -61,6 +63,8 @@ class Migration27Test : AbstractMigrationTest() {
assertEquals(2, userLoginId)
assertEquals("Unit Jan", userName)
}
+
+ db.close()
}
@Test
@@ -73,7 +77,7 @@ class Migration27Test : AbstractMigrationTest() {
close()
}
- helper.runMigrationsAndValidate(dbName, 27, true, Migration27())
+ runMigrationsAndValidate(Migration27())
val db = getMigratedRoomDatabase()
val students = runBlocking { db.studentDao.loadAll() }
@@ -90,6 +94,8 @@ class Migration27Test : AbstractMigrationTest() {
assertEquals(333, userLoginId)
assertEquals("Unit Tomasz", userName)
}
+
+ db.close()
}
private fun createStudent(db: SupportSQLiteDatabase, id: Long, userLoginId: Int, studentName: String) {
diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration35Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration35Test.kt
index 883cdb81c..79c24f2e6 100644
--- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration35Test.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration35Test.kt
@@ -29,7 +29,7 @@ class Migration35Test : AbstractMigrationTest() {
close()
}
- helper.runMigrationsAndValidate(dbName, 35, true, Migration35(AppInfo()))
+ runMigrationsAndValidate(Migration35(AppInfo()))
val db = getMigratedRoomDatabase()
val students = runBlocking { db.studentDao.loadAll() }
@@ -38,6 +38,8 @@ class Migration35Test : AbstractMigrationTest() {
assertTrue { students[0].avatarColor in AppInfo().defaultColorsForAvatar }
assertTrue { students[1].avatarColor in AppInfo().defaultColorsForAvatar }
+
+ db.close()
}
private fun createStudent(db: SupportSQLiteDatabase, id: Long) {
diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt
new file mode 100644
index 000000000..1855e0d50
--- /dev/null
+++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt
@@ -0,0 +1,130 @@
+package io.github.wulkanowy.data.db.migrations
+
+import android.content.ContentValues
+import android.database.sqlite.SQLiteDatabase
+import android.os.Build
+import androidx.sqlite.db.SupportSQLiteDatabase
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.HiltTestApplication
+import io.github.wulkanowy.sdk.Sdk
+import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.*
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import kotlin.random.Random
+import kotlin.test.assertEquals
+
+@HiltAndroidTest
+@RunWith(RobolectricTestRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
+class Migration54Test : AbstractMigrationTest() {
+
+ @Test
+ fun `don't touch unrelated students`() = runTest {
+ with(helper.createDatabase(dbName, 53)) {
+ createStudent(1, STANDARD, "vulcan.net.pl", "rzeszow", "Jan Michniewicz")
+ createStudent(2, ADFSLight, "umt.tarnow.pl", "tarnow", "Joanna Marcinkiewicz")
+ close()
+ }
+
+ runMigrationsAndValidate(Migration54())
+ val db = getMigratedRoomDatabase()
+ val students = db.studentDao.loadAll()
+
+ assertEquals(2, students.size)
+ with(students[0]) {
+ assertEquals(STANDARD.name, loginType)
+ assertEquals("https://vulcan.net.pl", scrapperBaseUrl)
+ assertEquals("rzeszow", symbol)
+ }
+ with(students[1]) {
+ assertEquals(ADFSLight.name, loginType)
+ assertEquals("https://umt.tarnow.pl", scrapperBaseUrl)
+ assertEquals("tarnow", symbol)
+ }
+ db.close()
+ }
+
+ @Test
+ fun `remove tomaszow mazowiecki students`() = runTest {
+ with(helper.createDatabase(dbName, 53)) {
+ createStudent(1, STANDARD, "vulcan.net.pl", "rzeszow", "Jan Michniewicz")
+ createStudent(2, STANDARD, "vulcan.net.pl", "tomaszowmazowiecki", "Joanna Stec")
+ createStudent(3, STANDARD, "vulcan.net.pl", "tomaszowmazowiecki", "Kacper Morawiecki")
+ close()
+ }
+
+ runMigrationsAndValidate(Migration54())
+ val db = getMigratedRoomDatabase()
+ val students = db.studentDao.loadAll()
+ assertEquals(1, students.size)
+ with(students[0]) {
+ assertEquals("rzeszow", symbol)
+ }
+ db.close()
+ }
+
+ @Test
+ fun `migrate resman students`() = runTest {
+ with(helper.createDatabase(dbName, 53)) {
+ createStudent(1, ADFSLight, "resman.pl", "rzeszow", "Joanna Stec")
+ createStudent(2, ADFSLight, "resman.pl", "rzeszow", "Kacper Morawiecki")
+ createStudent(3, STANDARD, "vulcan.net.pl", "rzeszow", "Jan Michniewicz")
+ close()
+ }
+ runMigrationsAndValidate(Migration54())
+ val db = getMigratedRoomDatabase()
+ val students = db.studentDao.loadAll()
+ assertEquals(3, students.size)
+ with(students[0]) {
+ assertEquals(ADFSLightScoped.name, loginType)
+ assertEquals("https://vulcan.net.pl", scrapperBaseUrl)
+ assertEquals("rzeszowprojekt", symbol)
+ }
+ with(students[1]) {
+ assertEquals(ADFSLightScoped.name, loginType)
+ assertEquals("https://vulcan.net.pl", scrapperBaseUrl)
+ assertEquals("rzeszowprojekt", symbol)
+ }
+ db.close()
+ }
+
+ private fun SupportSQLiteDatabase.createStudent(
+ id: Long,
+ loginType: Sdk.ScrapperLoginType,
+ host: String,
+ symbol: String,
+ studentName: String,
+ ) {
+ insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
+ put("scrapper_base_url", "https://$host")
+ put("mobile_base_url", "")
+ put("login_type", loginType.name)
+ put("login_mode", "SCRAPPER")
+ put("certificate_key", "")
+ put("private_key", "")
+ put("is_parent", false)
+ put("email", "jan@fakelog.cf")
+ put("password", "******")
+ put("symbol", symbol)
+ put("student_id", Random.nextInt())
+ put("user_login_id", id)
+ put("user_name", studentName)
+ put("student_name", studentName)
+ put("school_id", "123")
+ put("school_short", "")
+ put("school_name", "")
+ put("class_name", "")
+ put("class_id", Random.nextInt())
+ put("is_current", false)
+ put("registration_date", "0")
+ put("id", id)
+ put("nick", "")
+ put("avatar_color", "")
+ })
+ }
+}
diff --git a/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt b/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt
new file mode 100644
index 000000000..a35e5d303
--- /dev/null
+++ b/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt
@@ -0,0 +1,143 @@
+package io.github.wulkanowy.data.mappers
+
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.Timetable
+import io.github.wulkanowy.sdk.pojo.Attendance
+import io.github.wulkanowy.sdk.scrapper.attendance.SentExcuse
+import org.junit.Test
+import java.time.Instant
+import java.time.LocalDate
+import kotlin.test.assertEquals
+
+class AttendanceMapperTest {
+
+ @Test
+ fun `map attendance when fallback is not necessary`() {
+ val attendance = listOf(
+ getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"),
+ getSdkAttendance(2, LocalDate.of(2022, 11, 17), "Oryginalna 2"),
+ )
+ val lessons = listOf(
+ getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
+ getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"),
+ )
+
+ val result = attendance.mapToEntities(getEntitySemester(), lessons)
+ assertEquals("Oryginalna 1", result[0].subject)
+ assertEquals("Oryginalna 2", result[1].subject)
+ }
+
+ @Test
+ fun `map attendance when fallback is not always necessary`() {
+ val attendance = listOf(
+ getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"),
+ getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""),
+ )
+ val lessons = listOf(
+ getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
+ getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"),
+ )
+
+ val result = attendance.mapToEntities(getEntitySemester(), lessons)
+ assertEquals("Oryginalna 1", result[0].subject)
+ assertEquals("Druga", result[1].subject)
+ }
+
+ @Test
+ fun `map attendance when fallback is sometimes empty`() {
+ val attendance = listOf(
+ getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"),
+ getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""),
+ )
+ val lessons = listOf(
+ getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
+ )
+
+ val result = attendance.mapToEntities(getEntitySemester(), lessons)
+ assertEquals("Oryginalna 1", result[0].subject)
+ assertEquals("", result[1].subject)
+ }
+
+ @Test
+ fun `map attendance when fallback is empty`() {
+ val attendance = listOf(
+ getSdkAttendance(1, LocalDate.of(2022, 11, 17), ""),
+ getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""),
+ )
+ val lessons = listOf(
+ getEntityTimetable(1, LocalDate.of(2022, 11, 18), "Pierwsza"),
+ getEntityTimetable(2, LocalDate.of(2022, 10, 17), "Druga"),
+ )
+
+ val result = attendance.mapToEntities(getEntitySemester(), lessons)
+ assertEquals("", result[0].subject)
+ assertEquals("", result[1].subject)
+ }
+
+ @Test
+ fun `map attendance with all subject fallback`() {
+ val attendance = listOf(
+ getSdkAttendance(1, LocalDate.of(2022, 11, 17)),
+ getSdkAttendance(2, LocalDate.of(2022, 11, 17)),
+ )
+ val lessons = listOf(
+ getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
+ getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"),
+ )
+
+ val result = attendance.mapToEntities(getEntitySemester(), lessons)
+ assertEquals("Pierwsza", result[0].subject)
+ assertEquals("Druga", result[1].subject)
+ }
+
+ private fun getSdkAttendance(number: Int, date: LocalDate, subject: String = "") = Attendance(
+ number = number,
+ name = "ABSENCE",
+ subject = subject,
+ date = date,
+ timeId = 1,
+ categoryId = 1,
+ deleted = false,
+ excuseStatus = SentExcuse.Status.WAITING,
+ excusable = false,
+ absence = false,
+ excused = false,
+ exemption = false,
+ lateness = false,
+ presence = false,
+ )
+
+ private fun getEntityTimetable(number: Int, date: LocalDate, subject: String = "") = Timetable(
+ number = number,
+ start = Instant.now(),
+ end = Instant.now(),
+ date = date,
+ subject = subject,
+ subjectOld = "",
+ group = "",
+ room = "",
+ roomOld = "",
+ teacher = "",
+ teacherOld = "",
+ info = "",
+ changes = false,
+ canceled = false,
+ studentId = 0,
+ diaryId = 0,
+ isStudentPlan = false,
+ )
+
+ private fun getEntitySemester() = Semester(
+ studentId = 0,
+ diaryId = 0,
+ kindergartenDiaryId = 0,
+ diaryName = "",
+ schoolYear = 0,
+ semesterId = 0,
+ semesterName = 0,
+ start = LocalDate.now(),
+ end = LocalDate.now(),
+ classId = 0,
+ unitId = 0
+ )
+}
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 7d22f7265..896491ef0 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
@@ -2,6 +2,7 @@ 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.db.dao.TimetableDao
import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.toFirstResult
@@ -29,6 +30,9 @@ class AttendanceRepositoryTest {
@MockK
private lateinit var attendanceDb: AttendanceDao
+ @MockK
+ private lateinit var timetableDb: TimetableDao
+
@MockK(relaxUnitFun = true)
private lateinit var refreshHelper: AutoRefreshHelper
@@ -51,8 +55,9 @@ class AttendanceRepositoryTest {
fun setUp() {
MockKAnnotations.init(this)
every { refreshHelper.shouldBeRefreshed(any()) } returns false
+ coEvery { timetableDb.load(any(), any(), any(), any()) } returns emptyList()
- attendanceRepository = AttendanceRepository(attendanceDb, sdk, refreshHelper)
+ attendanceRepository = AttendanceRepository(attendanceDb, timetableDb, sdk, refreshHelper)
}
@Test
@@ -60,8 +65,8 @@ class AttendanceRepositoryTest {
// prepare
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
- flowOf(remoteList.mapToEntities(semester)),
- flowOf(remoteList.mapToEntities(semester))
+ flowOf(remoteList.mapToEntities(semester, emptyList())),
+ flowOf(remoteList.mapToEntities(semester, emptyList()))
)
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { attendanceDb.deleteAll(any()) } just Runs
@@ -83,9 +88,9 @@ class AttendanceRepositoryTest {
// prepare
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
- flowOf(remoteList.dropLast(1).mapToEntities(semester)),
- flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result
- flowOf(remoteList.mapToEntities(semester))
+ flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())),
+ flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), // after fetch end before save result
+ flowOf(remoteList.mapToEntities(semester, emptyList()))
)
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { attendanceDb.deleteAll(any()) } just Runs
@@ -100,7 +105,7 @@ class AttendanceRepositoryTest {
coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) }
coVerify {
attendanceDb.insertAll(match {
- it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
+ it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
})
}
coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) }
@@ -111,9 +116,9 @@ class AttendanceRepositoryTest {
// prepare
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList.dropLast(1)
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
- flowOf(remoteList.mapToEntities(semester)),
- flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result
- flowOf(remoteList.dropLast(1).mapToEntities(semester))
+ flowOf(remoteList.mapToEntities(semester, emptyList())),
+ flowOf(remoteList.mapToEntities(semester, emptyList())), // after fetch end before save result
+ flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList()))
)
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { attendanceDb.deleteAll(any()) } just Runs
@@ -129,7 +134,7 @@ class AttendanceRepositoryTest {
coVerify { attendanceDb.insertAll(match { it.isEmpty() }) }
coVerify {
attendanceDb.deleteAll(match {
- it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
+ it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
})
}
}
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 24306bfeb..9a2c22fd6 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
@@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories
import android.content.Context
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.SharedPrefProvider
+import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.Message
@@ -10,6 +11,7 @@ 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.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.getMailboxEntity
import io.github.wulkanowy.getStudentEntity
import io.github.wulkanowy.sdk.Sdk
@@ -55,6 +57,12 @@ class MessageRepositoryTest {
@MockK
private lateinit var sharedPrefProvider: SharedPrefProvider
+ @MockK
+ private lateinit var mailboxDao: MailboxDao
+
+ @MockK
+ private lateinit var getMailboxByStudentUseCase: GetMailboxByStudentUseCase
+
private val student = getStudentEntity()
private val mailbox = getMailboxEntity()
@@ -74,26 +82,33 @@ class MessageRepositoryTest {
refreshHelper = refreshHelper,
sharedPrefProvider = sharedPrefProvider,
json = Json,
+ mailboxDao = mailboxDao,
+ getMailboxByStudentUseCase = getMailboxByStudentUseCase,
)
}
@Test
fun `get messages when fetched completely new message without notify`() = runBlocking {
- every { messageDb.loadAll(any(), any()) } returns flowOf(emptyList())
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(mailbox)
+ every { messageDb.loadAll(mailbox.globalKey, any()) } returns flowOf(emptyList())
coEvery { sdk.getMessages(Folder.RECEIVED, any()) } returns listOf(
- getMessageDto()
+ getMessageDto().copy(
+ unreadBy = 5,
+ readBy = 10,
+ )
)
coEvery { messageDb.deleteAll(any()) } just Runs
coEvery { messageDb.insertAll(any()) } returns listOf()
- repository.getMessages(
+ val res = repository.getMessages(
student = student,
mailbox = mailbox,
folder = MessageFolder.RECEIVED,
forceRefresh = true,
notify = false,
- ).toFirstResult().dataOrNull.orEmpty()
+ ).toFirstResult()
+ assertEquals(null, res.errorOrNull)
coVerify(exactly = 1) { messageDb.deleteAll(withArg { checkEquals(emptyList()) }) }
coVerify {
messageDb.insertAll(withArg {
@@ -141,7 +156,7 @@ class MessageRepositoryTest {
messageDb.loadMessageWithAttachment("v4")
} returnsMany listOf(flowOf(mWa), flowOf(mWaWithContent))
coEvery {
- sdk.getMessageDetails("v4")
+ sdk.getMessageDetails("v4", any())
} returns mockk {
every { sender } returns ""
every { recipients } returns listOf("")
@@ -187,13 +202,16 @@ class MessageRepositoryTest {
) = Message(
messageGlobalKey = "v4",
mailboxKey = "",
+ email = "",
correspondents = "",
messageId = messageId,
subject = "",
date = Instant.EPOCH,
folderId = 1,
unread = unread,
- hasAttachments = false
+ readBy = 1,
+ unreadBy = 1,
+ hasAttachments = false,
).apply {
this.content = content
}
@@ -209,6 +227,8 @@ class MessageRepositoryTest {
dateZoned = Instant.EPOCH.atZone(ZoneOffset.UTC),
folderId = 1,
unread = true,
+ readBy = 1,
+ unreadBy = 1,
hasAttachments = false,
)
}
diff --git a/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt
new file mode 100644
index 000000000..6db16d2f5
--- /dev/null
+++ b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt
@@ -0,0 +1,225 @@
+package io.github.wulkanowy.domain
+
+import io.github.wulkanowy.data.db.dao.MailboxDao
+import io.github.wulkanowy.data.db.entities.Mailbox
+import io.github.wulkanowy.data.db.entities.MailboxType
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
+import io.github.wulkanowy.sdk.Sdk
+import io.mockk.MockKAnnotations
+import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import java.time.Instant
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class GetMailboxByStudentUseCaseTest {
+
+ @MockK
+ private lateinit var mailboxDao: MailboxDao
+
+ private lateinit var systemUnderTest: GetMailboxByStudentUseCase
+
+ @Before
+ fun setUp() {
+ MockKAnnotations.init(this)
+
+ coEvery { mailboxDao.deleteAll(any()) } just Runs
+ coEvery { mailboxDao.insertAll(any()) } returns emptyList()
+ coEvery { mailboxDao.loadAll(any()) } returns emptyList()
+
+ systemUnderTest = GetMailboxByStudentUseCase(mailboxDao = mailboxDao)
+ }
+
+ @Test
+ fun `get mailbox that doesn't exist`() = runTest {
+ val student = getStudentEntity(
+ userName = "Stanisław Kowalski",
+ studentName = "Jan Kowalski",
+ )
+ coEvery { mailboxDao.loadAll(any()) } returns emptyList()
+
+ assertNull(systemUnderTest(student))
+ }
+
+ @Test
+ fun `get mailbox for user with additional spaces`() = runTest {
+ val student = getStudentEntity(
+ userName = " Stanisław Kowalski ",
+ studentName = " Jan Kowalski ",
+ )
+ val expectedMailbox = getMailboxEntity("Jan Kowalski ")
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(
+ expectedMailbox,
+ )
+
+ val selectedMailbox = systemUnderTest(student)
+ assertEquals(expectedMailbox, selectedMailbox)
+ }
+
+ @Test
+ fun `get mailbox for user with reversed name`() = runTest {
+ val student = getStudentEntity(
+ userName = "Kowalski Jan",
+ studentName = "Jan Kowalski",
+ )
+ val expectedMailbox = getMailboxEntity("Kowalski Jan")
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(expectedMailbox)
+
+ assertEquals(expectedMailbox, systemUnderTest(student))
+ }
+
+ @Test
+ fun `get mailbox for unique non-authorized student`() = runTest {
+ val student = getStudentEntity(
+ userName = "Stanisław Kowalski",
+ studentName = "J** K*******",
+ )
+ val expectedMailbox = getMailboxEntity("Jan Kowalski")
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(
+ expectedMailbox,
+ )
+
+ assertEquals(expectedMailbox, systemUnderTest(student))
+ }
+
+ @Test
+ fun `get mailbox for unique non-authorized student but with spaces`() = runTest {
+ val student = getStudentEntity(
+ userName = "Stanisław Kowalski",
+ studentName = "J** K*******",
+ )
+ val expectedMailbox = getMailboxEntity("Jan Kowalski")
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(
+ expectedMailbox,
+ )
+
+ assertEquals(expectedMailbox, systemUnderTest(student))
+ }
+
+ @Test
+ fun `get mailbox for not-unique non-authorized student`() = runTest {
+ val student = getStudentEntity(
+ userName = "Stanisław Kowalski",
+ studentName = "J** K*******",
+ )
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(
+ getMailboxEntity("Jan Kowalski"),
+ getMailboxEntity("Jan Kurowski"),
+ )
+
+ assertNull(systemUnderTest(student))
+ }
+
+ @Test
+ fun `get mailbox for student with uppercase name`() = runTest {
+ val student = getStudentEntity(
+ userName = "Mochoń Julia",
+ studentName = "KLAUDIA MOCHOŃ",
+ )
+ val expectedMailbox = getMailboxEntity("Klaudia Mochoń")
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(
+ expectedMailbox,
+ )
+
+ assertEquals(expectedMailbox, systemUnderTest(student))
+ }
+
+ @Test
+ fun `get mailbox for student with second name`() = runTest {
+ val student = getStudentEntity(
+ userName = "Fistaszek Karolina",
+ studentName = "Julia Fistaszek",
+ )
+ val expectedMailbox = getMailboxEntity("Julia Maria Fistaszek")
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(
+ expectedMailbox,
+ )
+
+ assertEquals(expectedMailbox, systemUnderTest(student))
+ }
+
+ @Test
+ fun `get mailbox for student with second name and uppercase`() = runTest {
+ val student = getStudentEntity(
+ userName = "BEDNAREK KAMIL",
+ studentName = "ALEKSANDRA BEDNAREK",
+ )
+ val expectedMailbox = getMailboxEntity("Aleksandra Anna Bednarek")
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(
+ expectedMailbox,
+ )
+
+ assertEquals(expectedMailbox, systemUnderTest(student))
+ }
+
+ @Test
+ fun `get mailbox for student with mailboxes from two different schools`() = runTest {
+ val student = getStudentEntity(
+ userName = "Kamil Bednarek",
+ studentName = "Kamil Bednarek",
+ schoolShortName = "CKZiU",
+ )
+ val mailbox1 = getMailboxEntity(
+ studentName = "Kamil Bednarek",
+ schoolShortName = "ZSTiO",
+ )
+ val mailbox2 = getMailboxEntity(
+ studentName = "Kamil Bednarek",
+ schoolShortName = "CKZiU",
+ )
+ coEvery { mailboxDao.loadAll(any()) } returns listOf(mailbox1, mailbox2)
+
+ assertEquals(mailbox2, systemUnderTest(student))
+ }
+
+ private fun getMailboxEntity(
+ studentName: String,
+ schoolShortName: String = "test",
+ ) = Mailbox(
+ globalKey = "",
+ fullName = "",
+ userName = "",
+ email = "",
+ schoolId = "",
+ symbol = "",
+ studentName = studentName,
+ schoolNameShort = schoolShortName,
+ type = MailboxType.STUDENT,
+ )
+
+ private fun getStudentEntity(
+ studentName: String,
+ userName: String,
+ schoolShortName: String = "test",
+ ) = Student(
+ scrapperBaseUrl = "http://fakelog.cf",
+ email = "jan@fakelog.cf",
+ certificateKey = "",
+ classId = 0,
+ className = "",
+ isCurrent = false,
+ isParent = false,
+ loginMode = Sdk.Mode.API.name,
+ loginType = Sdk.ScrapperLoginType.STANDARD.name,
+ mobileBaseUrl = "",
+ password = "",
+ privateKey = "",
+ registrationDate = Instant.now(),
+ schoolName = "",
+ schoolShortName = schoolShortName,
+ schoolSymbol = "",
+ studentId = 1,
+ studentName = studentName,
+ symbol = "",
+ userLoginId = 1,
+ userName = userName,
+ )
+}
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 9bcfb8b6c..bf2d9f2cc 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
@@ -1,9 +1,9 @@
package io.github.wulkanowy.ui.modules.login.form
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.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.sdk.scrapper.Scrapper
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
import io.mockk.*
@@ -12,7 +12,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.IOException
-import java.time.Instant
class LoginFormPresenterTest {
@@ -33,6 +32,15 @@ class LoginFormPresenterTest {
private lateinit var presenter: LoginFormPresenter
+ private val registerUser = RegisterUser(
+ email = "",
+ password = "",
+ login = "",
+ baseUrl = "",
+ loginType = Scrapper.LoginType.AUTO,
+ symbols = listOf(),
+ )
+
@Before
fun setUp() {
MockKAnnotations.init(this)
@@ -104,32 +112,9 @@ class LoginFormPresenterTest {
@Test
fun loginTest() {
- val studentTest = Student(
- email = "test@",
- password = "123",
- scrapperBaseUrl = "https://fakelog.cf/?email",
- loginType = "AUTO",
- studentName = "",
- schoolSymbol = "",
- schoolName = "",
- studentId = 0,
- classId = 1,
- isCurrent = false,
- symbol = "",
- registrationDate = Instant.now(),
- className = "",
- mobileBaseUrl = "",
- privateKey = "",
- certificateKey = "",
- loginMode = "",
- userLoginId = 0,
- schoolShortName = "",
- isParent = false,
- userName = ""
- )
- coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf(
- StudentWithSemesters(studentTest, emptyList())
- )
+ coEvery {
+ repository.getUserSubjectsFromScrapper(any(), any(), any(), any())
+ } returns registerUser
every { loginFormView.formUsernameValue } returns "@"
every { loginFormView.formPassValue } returns "123456"
@@ -146,7 +131,9 @@ class LoginFormPresenterTest {
@Test
fun loginEmptyTest() {
- coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf()
+ coEvery {
+ repository.getUserSubjectsFromScrapper(any(), any(), any(), any())
+ } returns registerUser
every { loginFormView.formUsernameValue } returns "@"
every { loginFormView.formPassValue } returns "123456"
every { loginFormView.formHostValue } returns "https://fakelog.cf/?email"
@@ -162,7 +149,9 @@ class LoginFormPresenterTest {
@Test
fun loginEmptyTwiceTest() {
- coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf()
+ coEvery {
+ repository.getUserSubjectsFromScrapper(any(), any(), any(), any())
+ } returns registerUser
every { loginFormView.formUsernameValue } returns "@"
every { loginFormView.formPassValue } returns "123456"
every { loginFormView.formHostValue } returns "https://fakelog.cf/?email"
@@ -180,7 +169,14 @@ class LoginFormPresenterTest {
@Test
fun loginErrorTest() {
val testException = IOException("test")
- coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } throws testException
+ coEvery {
+ repository.getUserSubjectsFromScrapper(
+ any(),
+ any(),
+ any(),
+ any()
+ )
+ } throws testException
every { loginFormView.formUsernameValue } returns "@"
every { loginFormView.formPassValue } returns "123456"
every { loginFormView.formHostValue } returns "https://fakelog.cf/?email"
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 e52ec3ae2..cf426a50b 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
@@ -1,18 +1,22 @@
package io.github.wulkanowy.ui.modules.login.studentselect
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.pojos.RegisterStudent
+import io.github.wulkanowy.data.pojos.RegisterSymbol
+import io.github.wulkanowy.data.pojos.RegisterUnit
+import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.sdk.scrapper.Scrapper
import io.github.wulkanowy.services.sync.SyncManager
+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.AppInfo
import io.mockk.*
import io.mockk.impl.annotations.MockK
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import java.time.Instant
class LoginStudentSelectPresenterTest {
@@ -22,7 +26,7 @@ class LoginStudentSelectPresenterTest {
@MockK(relaxed = true)
lateinit var errorHandler: LoginErrorHandler
- @MockK(relaxed = true)
+ @MockK
lateinit var loginStudentSelectView: LoginStudentSelectView
@MockK
@@ -34,33 +38,55 @@ class LoginStudentSelectPresenterTest {
@MockK(relaxed = true)
lateinit var syncManager: SyncManager
+ private val appInfo = AppInfo()
+
private lateinit var presenter: LoginStudentSelectPresenter
- private val testStudent by lazy {
- Student(
- email = "test",
- password = "test123",
- scrapperBaseUrl = "https://fakelog.cf",
- loginType = "AUTO",
- symbol = "",
- isCurrent = false,
- studentId = 0,
- schoolName = "",
- schoolSymbol = "",
- classId = 1,
- studentName = "",
- registrationDate = Instant.now(),
- className = "",
- loginMode = "",
- certificateKey = "",
- privateKey = "",
- mobileBaseUrl = "",
- schoolShortName = "",
- userLoginId = 1,
- isParent = false,
- userName = ""
- )
- }
+ private val loginData = LoginData(
+ login = "",
+ password = "",
+ baseUrl = "",
+ symbol = null,
+ )
+
+ private val subject = RegisterStudent(
+ studentId = 0,
+ studentName = "",
+ studentSecondName = "",
+ studentSurname = "",
+ className = "",
+ classId = 0,
+ isParent = false,
+ semesters = listOf(),
+ )
+
+ private val school = RegisterUnit(
+ userLoginId = 0,
+ schoolId = "",
+ schoolName = "",
+ schoolShortName = "",
+ parentIds = listOf(),
+ studentIds = listOf(),
+ employeeIds = listOf(),
+ error = null,
+ students = listOf(subject)
+ )
+
+ private val symbol = RegisterSymbol(
+ symbol = "",
+ error = null,
+ userName = "",
+ schools = listOf(school),
+ )
+
+ private val registerUser = RegisterUser(
+ email = "",
+ password = "",
+ login = "",
+ baseUrl = "",
+ loginType = Scrapper.LoginType.AUTO,
+ symbols = listOf(symbol),
+ )
private val testException by lazy { RuntimeException("Problem") }
@@ -69,46 +95,62 @@ class LoginStudentSelectPresenterTest {
MockKAnnotations.init(this)
clearMocks(studentRepository, loginStudentSelectView)
+
+ coEvery { studentRepository.getSavedStudents(false) } returns emptyList()
+
every { loginStudentSelectView.initView() } just Runs
- every { loginStudentSelectView.showContact(any()) } just Runs
+ every { loginStudentSelectView.symbols } returns emptyMap()
+
every { loginStudentSelectView.enableSignIn(any()) } just Runs
every { loginStudentSelectView.showProgress(any()) } just Runs
every { loginStudentSelectView.showContent(any()) } just Runs
- presenter = LoginStudentSelectPresenter(studentRepository, errorHandler, syncManager, analytics)
- presenter.onAttachView(loginStudentSelectView, emptyList())
+ presenter = LoginStudentSelectPresenter(
+ studentRepository = studentRepository,
+ loginErrorHandler = errorHandler,
+ syncManager = syncManager,
+ analytics = analytics,
+ appInfo = appInfo,
+ )
}
@Test
fun initViewTest() {
+ presenter.onAttachView(loginStudentSelectView, loginData, registerUser)
verify { loginStudentSelectView.initView() }
}
@Test
fun onSelectedStudentTest() {
- coEvery {
- studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList())))
- } just Runs
+ val itemsSlot = slot>()
+ every { loginStudentSelectView.updateData(capture(itemsSlot)) } just Runs
+ presenter.onAttachView(loginStudentSelectView, loginData, registerUser)
- every { loginStudentSelectView.openMainView() } just Runs
+ coEvery { studentRepository.saveStudents(any()) } just Runs
- presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false)
+ every { loginStudentSelectView.navigateToNext() } just Runs
+
+ itemsSlot.captured.filterIsInstance().first().let {
+ it.onClick(it)
+ }
presenter.onSignIn()
verify { loginStudentSelectView.showContent(false) }
verify { loginStudentSelectView.showProgress(true) }
- verify { loginStudentSelectView.openMainView() }
+ verify { loginStudentSelectView.navigateToNext() }
}
@Test
fun onSelectedStudentErrorTest() {
- coEvery {
- studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList())))
- } throws testException
+ val itemsSlot = slot>()
+ every { loginStudentSelectView.updateData(capture(itemsSlot)) } just Runs
+ presenter.onAttachView(loginStudentSelectView, loginData, registerUser)
- coEvery { studentRepository.logoutStudent(testStudent) } just Runs
+ coEvery { studentRepository.saveStudents(any()) } throws testException
- presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false)
+ itemsSlot.captured.filterIsInstance().first().let {
+ it.onClick(it)
+ }
presenter.onSignIn()
verify { loginStudentSelectView.showContent(false) }
diff --git a/app/src/test/java/io/github/wulkanowy/utils/SemesterExtensionKtTest.kt b/app/src/test/java/io/github/wulkanowy/utils/SemesterExtensionKtTest.kt
new file mode 100644
index 000000000..b7d3ecc94
--- /dev/null
+++ b/app/src/test/java/io/github/wulkanowy/utils/SemesterExtensionKtTest.kt
@@ -0,0 +1,72 @@
+package io.github.wulkanowy.utils
+
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.getSemesterEntity
+import org.junit.Test
+import java.time.LocalDate
+import kotlin.test.assertEquals
+
+class SemesterExtensionKtTest {
+
+ @Test(expected = IllegalArgumentException::class)
+ fun `get current semester when current is doubled`() {
+ val semesters = listOf(
+ getSemesterEntity(1, 1, LocalDate.now(), LocalDate.now()),
+ getSemesterEntity(1, 1, LocalDate.now(), LocalDate.now())
+ )
+
+ semesters.getCurrentOrLast()
+ }
+
+ @Test(expected = RuntimeException::class)
+ fun `get current semester when there is empty list`() {
+ val semesters = listOf()
+
+ semesters.getCurrentOrLast()
+ }
+
+ @Test
+ fun `get current kindergarten semester when there is no any current`() {
+ val semesters = listOf(
+ createSemesterEntity(
+ kindergartenDiaryId = 281,
+ schoolYear = 2020,
+ semesterId = 0,
+ start = LocalDate.of(2020, 9, 1),
+ end = LocalDate.of(2021, 8, 31),
+ ),
+ createSemesterEntity(
+ kindergartenDiaryId = 342,
+ schoolYear = 2021,
+ semesterId = 0,
+ start = LocalDate.of(2021, 9, 1),
+ end = LocalDate.of(2022, 8, 31),
+ ),
+ )
+
+ val res = semesters.getCurrentOrLast()
+
+ assertEquals(2021, res.schoolYear)
+ }
+
+ private fun createSemesterEntity(
+ diaryId: Int = 0,
+ kindergartenDiaryId: Int = 0,
+ semesterId: Int = 0,
+ schoolYear: Int = 0,
+ start: LocalDate = LocalDate.now(),
+ end: LocalDate = LocalDate.now().plusMonths(6),
+ ) = Semester(
+ studentId = 1,
+ diaryId = diaryId,
+ kindergartenDiaryId = kindergartenDiaryId,
+ semesterId = semesterId,
+ diaryName = "$semesterId",
+ schoolYear = schoolYear,
+ classId = 0,
+ semesterName = semesterId,
+ unitId = 1,
+ start = start,
+ end = end
+ )
+}
diff --git a/build.gradle b/build.gradle
index 98c9dfb84..87e201acb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,8 +1,8 @@
buildscript {
ext {
- kotlin_version = '1.7.10'
- about_libraries = '10.4.0'
- hilt_version = "2.43.2"
+ kotlin_version = '1.7.21'
+ about_libraries = '10.5.2'
+ hilt_version = "2.44.2"
}
repositories {
mavenCentral()
@@ -13,14 +13,14 @@ 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.2.2'
+ classpath 'com.android.tools.build:gradle:7.3.1'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
- classpath 'com.google.gms:google-services:4.3.13'
- classpath 'com.huawei.agconnect:agcp:1.7.1.300'
- classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1'
+ classpath 'com.google.gms:google-services:4.3.14'
+ classpath 'com.huawei.agconnect:agcp:1.7.3.302'
+ classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
classpath "com.github.triplet.gradle:play-publisher:3.6.0"
classpath "ru.cian:huawei-publish-gradle-plugin:1.3.4"
- classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513"
+ classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"
}
@@ -31,6 +31,7 @@ allprojects {
mavenCentral()
google()
maven { url "https://jitpack.io" }
+ maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://developer.huawei.com/repo/" }
}
}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7454180f2..41d9927a4 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d7e66b5c6..f42e62f37 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.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists