diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..cdce0759b
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,4 @@
+# These are supported funding model platforms
+
+github: wulkanowy
+custom: https://www.paypal.com/paypalme/wulkanowy
diff --git a/.gitignore b/.gitignore
index 921bd0a9a..980085e38 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,6 +65,8 @@ captures/
.idea/uiDesigner.xml
.idea/runConfigurations.xml
.idea/discord.xml
+.idea/migrations.xml
+.idea/androidTestResultsUserPreferences.xml
# Keystore files
*.jks
diff --git a/app/build.gradle b/app/build.gradle
index 0fcedc68e..07efeb2f2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
- versionCode 136
- versionName "2.2.4"
+ versionCode 148
+ versionName "2.4.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@@ -113,6 +113,7 @@ android {
buildFeatures {
viewBinding true
+ buildConfig true
}
bundle {
@@ -141,7 +142,9 @@ android {
packagingOptions {
resources {
excludes += ['META-INF/library_release.kotlin_module',
- 'META-INF/library-core_release.kotlin_module']
+ 'META-INF/library-core_release.kotlin_module',
+ 'META-INF/LICENSE.md',
+ 'META-INF/LICENSE-notice.md']
}
}
@@ -161,8 +164,8 @@ play {
defaultToAppBundles = false
track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS
- userFraction = 0.01d
- updatePriority = 0
+ userFraction = 0.99d
+ updatePriority = 2
enabled.set(false)
}
@@ -183,28 +186,28 @@ huaweiPublish {
}
ext {
- work_manager = "2.8.1"
- android_hilt = "1.0.0"
- room = "2.6.0"
- chucker = "3.5.2"
- mockk = "1.13.8"
- coroutines = "1.7.3"
+ work_manager = "2.9.0"
+ android_hilt = "1.2.0"
+ room = "2.6.1"
+ chucker = "4.0.0"
+ mockk = "1.13.9"
+ coroutines = "1.8.0"
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.2.4'
+ implementation 'io.github.wulkanowy:sdk:2.4.2-SNAPSHOT'
- coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
- implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0"
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.core:core-splashscreen:1.0.1'
- implementation "androidx.activity:activity-ktx:1.8.0"
+ implementation "androidx.activity:activity-ktx:1.8.2"
implementation "androidx.appcompat:appcompat:1.6.1"
- implementation "androidx.fragment:fragment-ktx:1.6.1"
- implementation "androidx.annotation:annotation:1.7.0"
+ implementation "androidx.fragment:fragment-ktx:1.6.2"
+ implementation "androidx.annotation:annotation:1.7.1"
implementation "androidx.preference:preference-ktx:1.2.1"
implementation "androidx.recyclerview:recyclerview:1.3.2"
@@ -217,10 +220,10 @@ dependencies {
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.github.lopspower:CircularImageView:4.3.0'
- implementation "androidx.work:work-runtime-ktx:$work_manager"
+ implementation "androidx.work:work-runtime:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room"
@@ -237,33 +240,36 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
+ implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
implementation "com.jakewharton.timber:timber:5.0.1"
- implementation "at.favre.lib:slf4j-timber:1.0.1"
+ implementation 'com.github.Faierbel:slf4j-timber:2.0'
implementation 'com.github.bastienpaulfr:Treessence:1.1.2'
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
- implementation "io.coil-kt:coil:2.4.0"
+ implementation 'io.coil-kt:coil:2.6.0'
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
- implementation 'org.apache.commons:commons-text:1.10.0'
+ implementation 'org.apache.commons:commons-text:1.11.0'
- playImplementation platform('com.google.firebase:firebase-bom:32.4.0')
- playImplementation 'com.google.firebase:firebase-analytics-ktx'
- playImplementation 'com.google.firebase:firebase-messaging:'
+ playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
+ playImplementation 'com.google.firebase:firebase-analytics'
+ playImplementation 'com.google.firebase:firebase-messaging'
playImplementation 'com.google.firebase:firebase-crashlytics:'
- playImplementation 'com.google.firebase:firebase-config-ktx'
- playImplementation 'com.google.android.gms:play-services-ads:22.4.0'
- playImplementation "com.google.android.play:integrity:1.2.0"
+ playImplementation 'com.google.firebase:firebase-config'
+
+ playImplementation 'com.google.android.gms:play-services-ads:22.6.0'
+ playImplementation "com.google.android.play:integrity:1.3.0"
playImplementation 'com.google.android.play:app-update-ktx:2.1.0'
playImplementation 'com.google.android.play:review-ktx:2.0.1'
+ playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
- hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300'
- hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.301'
+ hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301'
+ hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
- releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
+ releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker"
- debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
+ debugImplementation "com.github.chuckerteam.chucker:library:$chucker"
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6'
debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha04'
@@ -272,7 +278,7 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
- testImplementation 'org.robolectric:robolectric:4.10.3'
+ testImplementation 'org.robolectric:robolectric:4.11.1'
testImplementation "androidx.test:runner:1.5.2"
testImplementation "androidx.test.ext:junit:1.1.5"
testImplementation "androidx.test:core:1.5.0"
diff --git a/app/jacoco.gradle b/app/jacoco.gradle
index 434b9218b..67ffdb13b 100644
--- a/app/jacoco.gradle
+++ b/app/jacoco.gradle
@@ -1,7 +1,7 @@
apply plugin: "jacoco"
jacoco {
- toolVersion "0.8.10"
+ toolVersion "0.8.11"
reportsDirectory.set(file("$buildDir/reports"))
}
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json
new file mode 100644
index 000000000..e6e71229c
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json
@@ -0,0 +1,2451 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 58,
+ "identityHash": "cd1d4f8f2b6e3860fbc1de93d4f5ca42",
+ "entities": [
+ {
+ "tableName": "Students",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `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": "scrapperDomainSuffix",
+ "columnName": "scrapper_domain_suffix",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": false,
+ "columnNames": [
+ "message_global_key",
+ "url",
+ "filename"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": false,
+ "columnNames": [
+ "globalKey"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "types",
+ "columnName": "types",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'[]'"
+ },
+ {
+ "fieldPath": "isOkVisible",
+ "columnName": "is_ok_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isXVisible",
+ "columnName": "is_x_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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, 'cd1d4f8f2b6e3860fbc1de93d4f5ca42')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json
new file mode 100644
index 000000000..a3f2e0dc6
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json
@@ -0,0 +1,2501 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 59,
+ "identityHash": "3bd95e40b587e8131a2a2c23aee538c1",
+ "entities": [
+ {
+ "tableName": "Students",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `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": "scrapperDomainSuffix",
+ "columnName": "scrapper_domain_suffix",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": false,
+ "columnNames": [
+ "message_global_key",
+ "url",
+ "filename"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": false,
+ "columnNames": [
+ "globalKey"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "types",
+ "columnName": "types",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'[]'"
+ },
+ {
+ "fieldPath": "isOkVisible",
+ "columnName": "is_ok_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isXVisible",
+ "columnName": "is_x_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesDescriptive",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT 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": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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, '3bd95e40b587e8131a2a2c23aee538c1')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json
new file mode 100644
index 000000000..20eacad1c
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json
@@ -0,0 +1,2527 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 60,
+ "identityHash": "3672d3f4d5e6b874e5a22d2bb458dc65",
+ "entities": [
+ {
+ "tableName": "Students",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `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": "scrapperDomainSuffix",
+ "columnName": "scrapper_domain_suffix",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": false,
+ "columnNames": [
+ "message_global_key",
+ "url",
+ "filename"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": false,
+ "columnNames": [
+ "globalKey"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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, `types` TEXT NOT NULL DEFAULT '[]', `is_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "types",
+ "columnName": "types",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'[]'"
+ },
+ {
+ "fieldPath": "isOkVisible",
+ "columnName": "is_ok_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isXVisible",
+ "columnName": "is_x_visible",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MutedMessageSenders",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "author",
+ "columnName": "author",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesDescriptive",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT 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": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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, '3672d3f4d5e6b874e5a22d2bb458dc65')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt
index 0c47e6bb6..1b0319f69 100644
--- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt
+++ b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt
@@ -14,34 +14,37 @@ import kotlin.test.assertFailsWith
@RunWith(AndroidJUnit4::class)
class ScramblerTest {
+ private val scrambler = Scrambler(ApplicationProvider.getApplicationContext())
+
@Test
fun encryptDecryptTest() {
- assertEquals("TEST", decrypt(encrypt("TEST",
- ApplicationProvider.getApplicationContext())))
+ assertEquals(
+ "TEST", scrambler.decrypt(scrambler.encrypt("TEST"))
+ )
}
@Test
fun emptyTextEncryptTest() {
assertFailsWith {
- decrypt("")
+ scrambler.decrypt("")
}
assertFailsWith {
- encrypt("", ApplicationProvider.getApplicationContext())
+ scrambler.encrypt("")
}
}
@Test
@SdkSuppress(minSdkVersion = 18)
fun emptyKeyStoreTest() {
- val text = encrypt("test", ApplicationProvider.getApplicationContext())
+ val text = scrambler.encrypt("test")
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
keyStore.deleteEntry("wulkanowy_password")
assertFailsWith {
- decrypt(text)
+ scrambler.decrypt(text)
}
}
}
diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt
index 461d29951..3a3b5948f 100644
--- a/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt
+++ b/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt
@@ -5,6 +5,7 @@ import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
+import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
@Suppress("unused")
@@ -13,9 +14,11 @@ class AdsHelper @Inject constructor(
private val preferencesRepository: PreferencesRepository
) {
+ val isMobileAdsSdkInitialized = MutableStateFlow(false)
+ val canShowAd = false
+
fun initialize() {
preferencesRepository.isAdsEnabled = false
- preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
}
diff --git a/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt
index 0e9227022..165a6204f 100644
--- a/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt
+++ b/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt
@@ -5,6 +5,7 @@ import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
+import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
@Suppress("unused")
@@ -12,10 +13,11 @@ class AdsHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository
) {
+ val isMobileAdsSdkInitialized = MutableStateFlow(false)
+ val canShowAd = false
fun initialize() {
preferencesRepository.isAdsEnabled = false
- preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 174c9a1fc..f43dfdd2c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,6 +44,7 @@
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="false"
android:theme="@style/WulkanowyTheme"
+ android:resizeableActivity="true"
tools:ignore="DataExtractionRules,UnusedAttribute">
Resource.dataOrNull: T?
get() = when (this) {
is Resource.Success -> this.data
is Resource.Intermediate -> this.data
- is Resource.Loading -> null
- is Resource.Error -> null
+ else -> null
+ }
+
+val Resource.dataOrThrow: T
+ get() = when (this) {
+ is Resource.Success -> this.data
+ is Resource.Intermediate -> this.data
+ is Resource.Loading -> throw IllegalStateException("Resource is in loading state")
+ is Resource.Error -> throw this.error
}
val Resource.errorOrNull: Throwable?
@@ -131,7 +148,7 @@ inline fun networkBoundResource(
query().map { Resource.Success(filterResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
- query().map { Resource.Error(throwable) }
+ flowOf(Resource.Error(throwable))
}
} else {
query().map { Resource.Success(filterResult(it)) }
@@ -165,7 +182,7 @@ inline fun networkBoundResource(
query().map { Resource.Success(mapResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
- query().map { Resource.Error(throwable) }
+ flowOf(Resource.Error(throwable))
}
} else {
query().map { Resource.Success(mapResult(it)) }
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 48a2942c9..21a6e3f3e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
@@ -1,11 +1,128 @@
package io.github.wulkanowy.data.db
import android.content.Context
-import androidx.room.*
+import androidx.room.AutoMigration
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
-import io.github.wulkanowy.data.db.dao.*
-import io.github.wulkanowy.data.db.entities.*
-import io.github.wulkanowy.data.db.migrations.*
+import androidx.room.TypeConverters
+import io.github.wulkanowy.data.db.dao.AdminMessageDao
+import io.github.wulkanowy.data.db.dao.AttendanceDao
+import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
+import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
+import io.github.wulkanowy.data.db.dao.ConferenceDao
+import io.github.wulkanowy.data.db.dao.ExamDao
+import io.github.wulkanowy.data.db.dao.GradeDao
+import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
+import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
+import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
+import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
+import io.github.wulkanowy.data.db.dao.GradeSummaryDao
+import io.github.wulkanowy.data.db.dao.HomeworkDao
+import io.github.wulkanowy.data.db.dao.LuckyNumberDao
+import io.github.wulkanowy.data.db.dao.MailboxDao
+import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
+import io.github.wulkanowy.data.db.dao.MessagesDao
+import io.github.wulkanowy.data.db.dao.MobileDeviceDao
+import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
+import io.github.wulkanowy.data.db.dao.NoteDao
+import io.github.wulkanowy.data.db.dao.NotificationDao
+import io.github.wulkanowy.data.db.dao.RecipientDao
+import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
+import io.github.wulkanowy.data.db.dao.SchoolDao
+import io.github.wulkanowy.data.db.dao.SemesterDao
+import io.github.wulkanowy.data.db.dao.StudentDao
+import io.github.wulkanowy.data.db.dao.StudentInfoDao
+import io.github.wulkanowy.data.db.dao.SubjectDao
+import io.github.wulkanowy.data.db.dao.TeacherDao
+import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
+import io.github.wulkanowy.data.db.dao.TimetableDao
+import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
+import io.github.wulkanowy.data.db.entities.AdminMessage
+import io.github.wulkanowy.data.db.entities.Attendance
+import io.github.wulkanowy.data.db.entities.AttendanceSummary
+import io.github.wulkanowy.data.db.entities.CompletedLesson
+import io.github.wulkanowy.data.db.entities.Conference
+import io.github.wulkanowy.data.db.entities.Exam
+import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
+import io.github.wulkanowy.data.db.entities.GradePartialStatistics
+import io.github.wulkanowy.data.db.entities.GradePointsStatistics
+import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
+import io.github.wulkanowy.data.db.entities.GradeSummary
+import io.github.wulkanowy.data.db.entities.Homework
+import io.github.wulkanowy.data.db.entities.LuckyNumber
+import io.github.wulkanowy.data.db.entities.Mailbox
+import io.github.wulkanowy.data.db.entities.Message
+import io.github.wulkanowy.data.db.entities.MessageAttachment
+import io.github.wulkanowy.data.db.entities.MobileDevice
+import io.github.wulkanowy.data.db.entities.MutedMessageSender
+import io.github.wulkanowy.data.db.entities.Note
+import io.github.wulkanowy.data.db.entities.Notification
+import io.github.wulkanowy.data.db.entities.Recipient
+import io.github.wulkanowy.data.db.entities.School
+import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.StudentInfo
+import io.github.wulkanowy.data.db.entities.Subject
+import io.github.wulkanowy.data.db.entities.Teacher
+import io.github.wulkanowy.data.db.entities.Timetable
+import io.github.wulkanowy.data.db.entities.TimetableAdditional
+import io.github.wulkanowy.data.db.entities.TimetableHeader
+import io.github.wulkanowy.data.db.migrations.Migration10
+import io.github.wulkanowy.data.db.migrations.Migration11
+import io.github.wulkanowy.data.db.migrations.Migration12
+import io.github.wulkanowy.data.db.migrations.Migration13
+import io.github.wulkanowy.data.db.migrations.Migration14
+import io.github.wulkanowy.data.db.migrations.Migration15
+import io.github.wulkanowy.data.db.migrations.Migration16
+import io.github.wulkanowy.data.db.migrations.Migration17
+import io.github.wulkanowy.data.db.migrations.Migration18
+import io.github.wulkanowy.data.db.migrations.Migration19
+import io.github.wulkanowy.data.db.migrations.Migration2
+import io.github.wulkanowy.data.db.migrations.Migration20
+import io.github.wulkanowy.data.db.migrations.Migration21
+import io.github.wulkanowy.data.db.migrations.Migration22
+import io.github.wulkanowy.data.db.migrations.Migration23
+import io.github.wulkanowy.data.db.migrations.Migration24
+import io.github.wulkanowy.data.db.migrations.Migration25
+import io.github.wulkanowy.data.db.migrations.Migration26
+import io.github.wulkanowy.data.db.migrations.Migration27
+import io.github.wulkanowy.data.db.migrations.Migration28
+import io.github.wulkanowy.data.db.migrations.Migration29
+import io.github.wulkanowy.data.db.migrations.Migration3
+import io.github.wulkanowy.data.db.migrations.Migration30
+import io.github.wulkanowy.data.db.migrations.Migration31
+import io.github.wulkanowy.data.db.migrations.Migration32
+import io.github.wulkanowy.data.db.migrations.Migration33
+import io.github.wulkanowy.data.db.migrations.Migration34
+import io.github.wulkanowy.data.db.migrations.Migration35
+import io.github.wulkanowy.data.db.migrations.Migration36
+import io.github.wulkanowy.data.db.migrations.Migration37
+import io.github.wulkanowy.data.db.migrations.Migration38
+import io.github.wulkanowy.data.db.migrations.Migration39
+import io.github.wulkanowy.data.db.migrations.Migration4
+import io.github.wulkanowy.data.db.migrations.Migration40
+import io.github.wulkanowy.data.db.migrations.Migration41
+import io.github.wulkanowy.data.db.migrations.Migration42
+import io.github.wulkanowy.data.db.migrations.Migration43
+import io.github.wulkanowy.data.db.migrations.Migration44
+import io.github.wulkanowy.data.db.migrations.Migration46
+import io.github.wulkanowy.data.db.migrations.Migration49
+import io.github.wulkanowy.data.db.migrations.Migration5
+import io.github.wulkanowy.data.db.migrations.Migration50
+import io.github.wulkanowy.data.db.migrations.Migration51
+import io.github.wulkanowy.data.db.migrations.Migration53
+import io.github.wulkanowy.data.db.migrations.Migration54
+import io.github.wulkanowy.data.db.migrations.Migration55
+import io.github.wulkanowy.data.db.migrations.Migration57
+import io.github.wulkanowy.data.db.migrations.Migration58
+import io.github.wulkanowy.data.db.migrations.Migration6
+import io.github.wulkanowy.data.db.migrations.Migration7
+import io.github.wulkanowy.data.db.migrations.Migration8
+import io.github.wulkanowy.data.db.migrations.Migration9
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton
@@ -41,7 +158,9 @@ import javax.inject.Singleton
TimetableHeader::class,
SchoolAnnouncement::class,
Notification::class,
- AdminMessage::class
+ AdminMessage::class,
+ MutedMessageSender::class,
+ GradeDescriptive::class,
],
autoMigrations = [
AutoMigration(from = 44, to = 45),
@@ -51,6 +170,9 @@ import javax.inject.Singleton
AutoMigration(from = 54, to = 55, spec = Migration55::class),
AutoMigration(from = 55, to = 56),
AutoMigration(from = 56, to = 57, spec = Migration57::class),
+ AutoMigration(from = 57, to = 58, spec = Migration58::class),
+ AutoMigration(from = 58, to = 59),
+ AutoMigration(from = 59, to = 60),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@@ -59,7 +181,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
- const val VERSION_SCHEMA = 57
+ const val VERSION_SCHEMA = 60
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@@ -184,4 +306,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract val notificationDao: NotificationDao
abstract val adminMessagesDao: AdminMessageDao
+
+ abstract val mutedMessageSendersDao: MutedMessageSendersDao
+
+ abstract val gradeDescriptiveDao: GradeDescriptiveDao
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt
new file mode 100644
index 000000000..6282c0804
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt
@@ -0,0 +1,15 @@
+package io.github.wulkanowy.data.db.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Singleton
+
+@Singleton
+@Dao
+interface GradeDescriptiveDao : BaseDao {
+
+ @Query("SELECT * FROM GradesDescriptive WHERE semester_id = :semesterId AND student_id = :studentId")
+ fun loadAll(semesterId: Int, studentId: Int): 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 1709f7636..11e6da1e7 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
@@ -5,15 +5,23 @@ import androidx.room.Query
import androidx.room.Transaction
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
+import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
import kotlinx.coroutines.flow.Flow
@Dao
interface MessagesDao : BaseDao {
-
@Transaction
@Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
fun loadMessageWithAttachment(messageGlobalKey: String): Flow
+ @Transaction
+ @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
+ fun loadMessagesWithMutedAuthor(mailboxKey: String, folder: Int): Flow>
+
+ @Transaction
+ @Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
+ fun loadMessagesWithMutedAuthor(folder: Int, email: String): Flow>
+
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
fun loadAll(mailboxKey: String, folder: Int): Flow>
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt
new file mode 100644
index 000000000..0a8664010
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt
@@ -0,0 +1,20 @@
+package io.github.wulkanowy.data.db.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import io.github.wulkanowy.data.db.entities.MutedMessageSender
+
+@Dao
+interface MutedMessageSendersDao : BaseDao {
+
+ @Query("SELECT COUNT(*) FROM MutedMessageSenders WHERE author = :author")
+ suspend fun checkMute(author: String): Boolean
+
+ @Insert(onConflict = OnConflictStrategy.IGNORE)
+ suspend fun insertMute(mute: MutedMessageSender): Long
+
+ @Query("DELETE FROM MutedMessageSenders WHERE author = :author")
+ suspend fun deleteMute(author: String)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt
index d7847c240..d9326ff6c 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt
@@ -1,11 +1,16 @@
package io.github.wulkanowy.data.db.dao
-import androidx.room.*
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Transaction
+import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentName
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
-import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import javax.inject.Singleton
@Singleton
@@ -47,6 +52,9 @@ abstract class StudentDao {
@Query("UPDATE Students SET is_current = 0")
abstract suspend fun resetCurrent()
+ @Query("DELETE FROM Students WHERE email = :email AND user_name = :userName")
+ abstract suspend fun deleteByEmailAndUserName(email: String, userName: String)
+
@Transaction
open suspend fun switchCurrent(id: Long) {
resetCurrent()
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 b4b7379f2..40d97ea96 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
@@ -15,5 +15,5 @@ interface TimetableDao : BaseDao {
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
+ suspend fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
index 875c2a3a5..0c8f1a5d1 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
@@ -37,6 +37,9 @@ data class AdminMessage(
@ColumnInfo(name = "types", defaultValue = "[]")
val types: List = emptyList(),
- @ColumnInfo(name = "is_dismissible")
- val isDismissible: Boolean = false
+ @ColumnInfo(name = "is_ok_visible", defaultValue = "0")
+ val isOkVisible: Boolean = false,
+
+ @ColumnInfo(name = "is_x_visible", defaultValue = "0")
+ val isXVisible: Boolean = false
)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt
new file mode 100644
index 000000000..9aec9599a
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt
@@ -0,0 +1,27 @@
+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 = "GradesDescriptive")
+data class GradeDescriptive(
+
+ @ColumnInfo(name = "semester_id")
+ val semesterId: Int,
+
+ @ColumnInfo(name = "student_id")
+ val studentId: Int,
+
+ val subject: String,
+
+ val description: String,
+) : Serializable {
+
+ @PrimaryKey(autoGenerate = true)
+ var id: Long = 0
+
+ @ColumnInfo(name = "is_notified")
+ var isNotified: Boolean = true
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt
index cd468215d..fc890e760 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt
@@ -2,11 +2,15 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.Embedded
import androidx.room.Relation
+import java.io.Serializable
data class MessageWithAttachment(
@Embedded
val message: Message,
@Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
- val attachments: List
-)
+ val attachments: List,
+
+ @Relation(parentColumn = "correspondents", entityColumn = "author")
+ val mutedMessageSender: MutedMessageSender?,
+) : Serializable
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt
new file mode 100644
index 000000000..e3cd1ca7d
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt
@@ -0,0 +1,12 @@
+package io.github.wulkanowy.data.db.entities
+
+import androidx.room.Embedded
+import androidx.room.Relation
+
+data class MessageWithMutedAuthor(
+ @Embedded
+ val message: Message,
+
+ @Relation(parentColumn = "correspondents", entityColumn = "author")
+ val mutedMessageSender: MutedMessageSender?,
+)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt
new file mode 100644
index 000000000..f1770e64c
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt
@@ -0,0 +1,15 @@
+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 = "MutedMessageSenders")
+data class MutedMessageSender(
+ @ColumnInfo(name = "author")
+ val author: String,
+) : Serializable {
+ @PrimaryKey(autoGenerate = true)
+ var id: Long = 0
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt
index c26a02d1f..0e7e14097 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt
@@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration10 : Migration(9, 10) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Grades_Summary RENAME TO GradesSummary")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Grades_Summary RENAME TO GradesSummary")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt
index 6d129bca0..342e2e2e3 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration11 : Migration(10, 11) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Grades_temp (
id INTEGER PRIMARY KEY NOT NULL,
is_read INTEGER NOT NULL,
@@ -26,9 +27,10 @@ class Migration11 : Migration(10, 11) {
date INTEGER NOT NULL,
teacher TEXT NOT NULL
)
- """)
- database.execSQL("INSERT INTO Grades_temp SELECT * FROM Grades")
- database.execSQL("DROP TABLE Grades")
- database.execSQL("ALTER TABLE Grades_temp RENAME TO Grades")
+ """
+ )
+ db.execSQL("INSERT INTO Grades_temp SELECT * FROM Grades")
+ db.execSQL("DROP TABLE Grades")
+ db.execSQL("ALTER TABLE Grades_temp RENAME TO Grades")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt
index c827b82ba..6cc726953 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt
@@ -5,16 +5,17 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration12 : Migration(11, 12) {
- override fun migrate(database: SupportSQLiteDatabase) {
- createTempStudentsTable(database)
- replaceStudentTable(database)
- updateStudentsWithClassId(database, getStudentsIds(database))
- removeStudentsWithNoClassId(database)
- ensureThereIsOnlyOneCurrentStudent(database)
+ override fun migrate(db: SupportSQLiteDatabase) {
+ createTempStudentsTable(db)
+ replaceStudentTable(db)
+ updateStudentsWithClassId(db, getStudentsIds(db))
+ removeStudentsWithNoClassId(db)
+ ensureThereIsOnlyOneCurrentStudent(db)
}
- private fun createTempStudentsTable(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ private fun createTempStudentsTable(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Students_tmp (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
endpoint TEXT NOT NULL,
@@ -30,15 +31,16 @@ class Migration12 : Migration(11, 12) {
registration_date INTEGER NOT NULL,
class_id INTEGER NOT NULL
)
- """)
- database.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students_tmp (email, symbol, student_id, school_id, class_id)")
+ """
+ )
+ db.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students_tmp (email, symbol, student_id, school_id, class_id)")
}
- private fun replaceStudentTable(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Students ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL")
- database.execSQL("INSERT INTO Students_tmp SELECT * FROM Students")
- database.execSQL("DROP TABLE Students")
- database.execSQL("ALTER TABLE Students_tmp RENAME TO Students")
+ private fun replaceStudentTable(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Students ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL")
+ db.execSQL("INSERT INTO Students_tmp SELECT * FROM Students")
+ db.execSQL("DROP TABLE Students")
+ db.execSQL("ALTER TABLE Students_tmp RENAME TO Students")
}
private fun getStudentsIds(database: SupportSQLiteDatabase): List {
@@ -54,18 +56,18 @@ class Migration12 : Migration(11, 12) {
return students
}
- private fun updateStudentsWithClassId(database: SupportSQLiteDatabase, students: List) {
+ private fun updateStudentsWithClassId(db: SupportSQLiteDatabase, students: List) {
students.forEach {
- database.execSQL("UPDATE Students SET class_id = IFNULL((SELECT class_id FROM Semesters WHERE student_id = $it), 0) WHERE student_id = $it")
+ db.execSQL("UPDATE Students SET class_id = IFNULL((SELECT class_id FROM Semesters WHERE student_id = $it), 0) WHERE student_id = $it")
}
}
- private fun removeStudentsWithNoClassId(database: SupportSQLiteDatabase) {
- database.execSQL("DELETE FROM Students WHERE class_id = 0")
+ private fun removeStudentsWithNoClassId(db: SupportSQLiteDatabase) {
+ db.execSQL("DELETE FROM Students WHERE class_id = 0")
}
- private fun ensureThereIsOnlyOneCurrentStudent(database: SupportSQLiteDatabase) {
- database.execSQL("UPDATE Students SET is_current = 0")
- database.execSQL("UPDATE Students SET is_current = 1 WHERE id = (SELECT MAX(id) FROM Students)")
+ private fun ensureThereIsOnlyOneCurrentStudent(db: SupportSQLiteDatabase) {
+ db.execSQL("UPDATE Students SET is_current = 0")
+ db.execSQL("UPDATE Students SET is_current = 1 WHERE id = (SELECT MAX(id) FROM Students)")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt
index 36de1e837..c5030232b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt
@@ -5,27 +5,30 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration13 : Migration(12, 13) {
- override fun migrate(database: SupportSQLiteDatabase) {
- addClassNameToStudents(database, getStudentsIds(database))
- updateSemestersTable(database)
- markAtLeastAndOnlyOneSemesterAtCurrent(database, getStudentsAndClassIds(database))
- clearMessagesTable(database)
+ override fun migrate(db: SupportSQLiteDatabase) {
+ addClassNameToStudents(db, getStudentsIds(db))
+ updateSemestersTable(db)
+ markAtLeastAndOnlyOneSemesterAtCurrent(db, getStudentsAndClassIds(db))
+ clearMessagesTable(db)
}
- private fun addClassNameToStudents(database: SupportSQLiteDatabase, students: List>) {
- database.execSQL("ALTER TABLE Students ADD COLUMN class_name TEXT DEFAULT \"\" NOT NULL")
+ private fun addClassNameToStudents(
+ db: SupportSQLiteDatabase,
+ students: List>
+ ) {
+ db.execSQL("ALTER TABLE Students ADD COLUMN class_name TEXT DEFAULT \"\" NOT NULL")
students.forEach { (id, name) ->
val schoolName = name.substringAfter(" - ")
val className = name.substringBefore(" - ", "").replace("Klasa ", "")
- database.execSQL("UPDATE Students SET class_name = '$className' WHERE id = '$id'")
- database.execSQL("UPDATE Students SET school_name = '$schoolName' WHERE id = '$id'")
+ db.execSQL("UPDATE Students SET class_name = '$className' WHERE id = '$id'")
+ db.execSQL("UPDATE Students SET school_name = '$schoolName' WHERE id = '$id'")
}
}
- private fun getStudentsIds(database: SupportSQLiteDatabase): MutableList> {
+ private fun getStudentsIds(db: SupportSQLiteDatabase): MutableList> {
val students = mutableListOf>()
- database.query("SELECT id, school_name FROM Students").use {
+ db.query("SELECT id, school_name FROM Students").use {
if (it.moveToFirst()) {
do {
students.add(it.getInt(0) to it.getString(1))
@@ -36,15 +39,15 @@ class Migration13 : Migration(12, 13) {
return students
}
- private fun updateSemestersTable(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Semesters ADD COLUMN school_year INTEGER DEFAULT 1970 NOT NULL")
- database.execSQL("ALTER TABLE Semesters ADD COLUMN start INTEGER DEFAULT 0 NOT NULL")
- database.execSQL("ALTER TABLE Semesters ADD COLUMN `end` INTEGER DEFAULT 0 NOT NULL")
+ private fun updateSemestersTable(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Semesters ADD COLUMN school_year INTEGER DEFAULT 1970 NOT NULL")
+ db.execSQL("ALTER TABLE Semesters ADD COLUMN start INTEGER DEFAULT 0 NOT NULL")
+ db.execSQL("ALTER TABLE Semesters ADD COLUMN `end` INTEGER DEFAULT 0 NOT NULL")
}
- private fun getStudentsAndClassIds(database: SupportSQLiteDatabase): List> {
+ private fun getStudentsAndClassIds(db: SupportSQLiteDatabase): List> {
val students = mutableListOf>()
- database.query("SELECT student_id, class_id FROM Students").use {
+ db.query("SELECT student_id, class_id FROM Students").use {
if (it.moveToFirst()) {
do {
students.add(it.getInt(0) to it.getInt(1))
@@ -55,14 +58,17 @@ class Migration13 : Migration(12, 13) {
return students
}
- private fun markAtLeastAndOnlyOneSemesterAtCurrent(database: SupportSQLiteDatabase, students: List>) {
+ private fun markAtLeastAndOnlyOneSemesterAtCurrent(
+ db: SupportSQLiteDatabase,
+ students: List>
+ ) {
students.forEach { (studentId, classId) ->
- database.execSQL("UPDATE Semesters SET is_current = 0 WHERE student_id = '$studentId' AND class_id = '$classId'")
- database.execSQL("UPDATE Semesters SET is_current = 1 WHERE id = (SELECT id FROM Semesters WHERE student_id = '$studentId' AND class_id = '$classId' ORDER BY semester_id DESC)")
+ db.execSQL("UPDATE Semesters SET is_current = 0 WHERE student_id = '$studentId' AND class_id = '$classId'")
+ db.execSQL("UPDATE Semesters SET is_current = 1 WHERE id = (SELECT id FROM Semesters WHERE student_id = '$studentId' AND class_id = '$classId' ORDER BY semester_id DESC)")
}
}
- private fun clearMessagesTable(database: SupportSQLiteDatabase) {
- database.execSQL("DELETE FROM Messages")
+ private fun clearMessagesTable(db: SupportSQLiteDatabase) {
+ db.execSQL("DELETE FROM Messages")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt
index 4dac0d306..793b4a9d2 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt
@@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration14 : Migration(13, 14) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS GradesSummary")
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS GradesSummary")
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS GradesSummary (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
semester_id INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt
index 5be49a95b..5ff44e9ca 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration15 : Migration(14, 15) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS MobileDevices (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
@@ -14,6 +15,7 @@ class Migration15 : Migration(14, 15) {
name TEXT NOT NULL,
date INTEGER NOT NULL
)
- """)
+ """
+ )
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt
index 7f40c0f8d..8a8f5b8f2 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration16 : Migration(15, 16) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Teachers (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
@@ -15,6 +16,7 @@ class Migration16 : Migration(15, 16) {
name TEXT NOT NULL,
short_name TEXT NOT NULL
)
- """)
+ """
+ )
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt
index e2a2574db..cf3318ad4 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt
@@ -5,13 +5,14 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration17 : Migration(16, 17) {
- override fun migrate(database: SupportSQLiteDatabase) {
- createGradesPointsStatisticsTable(database)
- truncateSemestersTable(database)
+ override fun migrate(db: SupportSQLiteDatabase) {
+ createGradesPointsStatisticsTable(db)
+ truncateSemestersTable(db)
}
- private fun createGradesPointsStatisticsTable(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ private fun createGradesPointsStatisticsTable(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS GradesPointsStatistics(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
@@ -20,10 +21,11 @@ class Migration17 : Migration(16, 17) {
others REAL NOT NULL,
student REAL NOT NULL
)
- """)
+ """
+ )
}
- private fun truncateSemestersTable(database: SupportSQLiteDatabase) {
- database.execSQL("DELETE FROM Semesters")
+ private fun truncateSemestersTable(db: SupportSQLiteDatabase) {
+ db.execSQL("DELETE FROM Semesters")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt
index 6c5e56c6a..713f8e724 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration18 : Migration(17, 18) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS School (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt
index d38f1245a..021cdbb37 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt
@@ -6,16 +6,17 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migration(18, 19) {
- override fun migrate(database: SupportSQLiteDatabase) {
- migrateMessages(database)
- migrateGrades(database)
- migrateStudents(database)
+ override fun migrate(db: SupportSQLiteDatabase) {
+ migrateMessages(db)
+ migrateGrades(db)
+ migrateStudents(db)
migrateSharedPreferences()
}
- private fun migrateMessages(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE Messages")
- database.execSQL("""
+ private fun migrateMessages(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE Messages")
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Messages (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
is_notified INTEGER NOT NULL,
@@ -34,12 +35,14 @@ class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migratio
read_by INTEGER NOT NULL,
removed INTEGER NOT NULL
)
- """)
+ """
+ )
}
- private fun migrateGrades(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE Grades")
- database.execSQL("""
+ private fun migrateGrades(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE Grades")
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Grades (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
is_read INTEGER NOT NULL,
@@ -59,11 +62,13 @@ class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migratio
date INTEGER NOT NULL,
teacher TEXT NOT NULL
)
- """)
+ """
+ )
}
- private fun migrateStudents(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ private fun migrateStudents(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Students_tmp (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
scrapper_base_url TEXT NOT NULL,
@@ -86,26 +91,29 @@ class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migratio
is_current INTEGER NOT NULL,
registration_date INTEGER NOT NULL
)
- """)
+ """
+ )
- database.execSQL("ALTER TABLE Students ADD COLUMN scrapperBaseUrl TEXT NOT NULL DEFAULT \"\";")
- database.execSQL("ALTER TABLE Students ADD COLUMN apiBaseUrl TEXT NOT NULL DEFAULT \"\";")
- database.execSQL("ALTER TABLE Students ADD COLUMN is_parent INT NOT NULL DEFAULT 0;")
- database.execSQL("ALTER TABLE Students ADD COLUMN loginMode TEXT NOT NULL DEFAULT \"\";")
- database.execSQL("ALTER TABLE Students ADD COLUMN certificateKey TEXT NOT NULL DEFAULT \"\";")
- database.execSQL("ALTER TABLE Students ADD COLUMN privateKey TEXT NOT NULL DEFAULT \"\";")
- database.execSQL("ALTER TABLE Students ADD COLUMN user_login_id INTEGER NOT NULL DEFAULT 0;")
+ db.execSQL("ALTER TABLE Students ADD COLUMN scrapperBaseUrl TEXT NOT NULL DEFAULT \"\";")
+ db.execSQL("ALTER TABLE Students ADD COLUMN apiBaseUrl TEXT NOT NULL DEFAULT \"\";")
+ db.execSQL("ALTER TABLE Students ADD COLUMN is_parent INT NOT NULL DEFAULT 0;")
+ db.execSQL("ALTER TABLE Students ADD COLUMN loginMode TEXT NOT NULL DEFAULT \"\";")
+ db.execSQL("ALTER TABLE Students ADD COLUMN certificateKey TEXT NOT NULL DEFAULT \"\";")
+ db.execSQL("ALTER TABLE Students ADD COLUMN privateKey TEXT NOT NULL DEFAULT \"\";")
+ db.execSQL("ALTER TABLE Students ADD COLUMN user_login_id INTEGER NOT NULL DEFAULT 0;")
- database.execSQL("""
+ db.execSQL(
+ """
INSERT INTO Students_tmp(
id, scrapper_base_url, mobile_base_url, is_parent, login_type, login_mode, certificate_key, private_key, email, password, symbol, student_id, user_login_id, student_name, school_id, school_name, school_id, school_name, class_name, class_id, is_current, registration_date)
SELECT
id, endpoint, apiBaseUrl, is_parent, loginType, "SCRAPPER", certificateKey, privateKey, email, password, symbol, student_id, user_login_id, student_name, school_id, school_name, school_id, school_name, class_name, class_id, is_current, registration_date
FROM Students
- """)
- database.execSQL("DROP TABLE Students")
- database.execSQL("ALTER TABLE Students_tmp RENAME TO Students")
- database.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students (email, symbol, student_id, school_id, class_id)")
+ """
+ )
+ db.execSQL("DROP TABLE Students")
+ db.execSQL("ALTER TABLE Students_tmp RENAME TO Students")
+ db.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students (email, symbol, student_id, school_id, class_id)")
}
private fun migrateSharedPreferences() {
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt
index c5a30991a..be8675092 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt
@@ -5,14 +5,16 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration2 : Migration(1, 2) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS LuckyNumbers (
id INTEGER PRIMARY KEY NOT NULL,
is_notified INTEGER NOT NULL,
student_id INTEGER NOT NULL,
date INTEGER NOT NULL,
lucky_number INTEGER NOT NULL)
- """)
+ """
+ )
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt
index 2fcfc183d..7ad43230b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt
@@ -5,14 +5,15 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration20 : Migration(19, 20) {
- override fun migrate(database: SupportSQLiteDatabase) {
- migrateTimetable(database)
- truncateSubjects(database)
+ override fun migrate(db: SupportSQLiteDatabase) {
+ migrateTimetable(db)
+ truncateSubjects(db)
}
- private fun migrateTimetable(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE Timetable")
- database.execSQL("""
+ private fun migrateTimetable(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE Timetable")
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS `Timetable` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`student_id` INTEGER NOT NULL,
@@ -33,10 +34,11 @@ class Migration20 : Migration(19, 20) {
`changes` INTEGER NOT NULL,
`canceled` INTEGER NOT NULL
)
- """)
+ """
+ )
}
- private fun truncateSubjects(database: SupportSQLiteDatabase) {
- database.execSQL("DELETE FROM Subjects")
+ private fun truncateSubjects(db: SupportSQLiteDatabase) {
+ db.execSQL("DELETE FROM Subjects")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt
index bc0ff900c..60e044cdc 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt
@@ -5,11 +5,11 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration21 : Migration(20, 21) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Attendance ADD COLUMN excusable INTEGER NOT NULL DEFAULT 0")
- database.execSQL("ALTER TABLE Attendance ADD COLUMN time_id INTEGER NOT NULL DEFAULT 0")
- database.execSQL("ALTER TABLE Attendance ADD COLUMN excuse_status TEXT DEFAULT NULL")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Attendance ADD COLUMN excusable INTEGER NOT NULL DEFAULT 0")
+ db.execSQL("ALTER TABLE Attendance ADD COLUMN time_id INTEGER NOT NULL DEFAULT 0")
+ db.execSQL("ALTER TABLE Attendance ADD COLUMN excuse_status TEXT DEFAULT NULL")
- database.execSQL("DELETE FROM Semesters")
+ db.execSQL("DELETE FROM Semesters")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt
index cf50a6c3e..ef525a49d 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt
@@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration22 : Migration(21, 22) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Students ADD COLUMN school_short TEXT NOT NULL DEFAULT ''")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Students ADD COLUMN school_short TEXT NOT NULL DEFAULT ''")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt
index 22de94c3f..3650307a6 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt
@@ -5,10 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration23 : Migration(22, 23) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Notes ADD COLUMN teacher_symbol TEXT NOT NULL DEFAULT ''")
- database.execSQL("ALTER TABLE Notes ADD COLUMN category_type INTEGER NOT NULL DEFAULT 0")
- database.execSQL("ALTER TABLE Notes ADD COLUMN is_points_show INTEGER NOT NULL DEFAULT 0")
- database.execSQL("ALTER TABLE Notes ADD COLUMN points INTEGER NOT NULL DEFAULT 0")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Notes ADD COLUMN teacher_symbol TEXT NOT NULL DEFAULT ''")
+ db.execSQL("ALTER TABLE Notes ADD COLUMN category_type INTEGER NOT NULL DEFAULT 0")
+ db.execSQL("ALTER TABLE Notes ADD COLUMN is_points_show INTEGER NOT NULL DEFAULT 0")
+ db.execSQL("ALTER TABLE Notes ADD COLUMN points INTEGER NOT NULL DEFAULT 0")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt
index 604ed4875..a3cd98197 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt
@@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration24 : Migration(23, 24) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Messages ADD COLUMN has_attachments INTEGER NOT NULL DEFAULT 0")
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Messages ADD COLUMN has_attachments INTEGER NOT NULL DEFAULT 0")
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS MessageAttachments (
real_id INTEGER NOT NULL,
message_id INTEGER NOT NULL,
@@ -16,6 +17,7 @@ class Migration24 : Migration(23, 24) {
filename TEXT NOT NULL,
PRIMARY KEY(real_id)
)
- """)
+ """
+ )
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt
index 4749bac73..cb395d7e0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt
@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration25 : Migration(24, 25) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Homework ADD COLUMN is_done INTEGER NOT NULL DEFAULT 0")
- database.execSQL("ALTER TABLE Homework ADD COLUMN attachments TEXT NOT NULL DEFAULT \"[]\"")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Homework ADD COLUMN is_done INTEGER NOT NULL DEFAULT 0")
+ db.execSQL("ALTER TABLE Homework ADD COLUMN attachments TEXT NOT NULL DEFAULT \"[]\"")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt
index 7130d86d8..94746b456 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt
@@ -5,10 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration26 : Migration(25, 26) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_predicted_grade_notified INTEGER NOT NULL DEFAULT 1")
- database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_final_grade_notified INTEGER NOT NULL DEFAULT 1")
- database.execSQL("ALTER TABLE GradesSummary ADD COLUMN predicted_grade_last_change INTEGER NOT NULL DEFAULT 0")
- database.execSQL("ALTER TABLE GradesSummary ADD COLUMN final_grade_last_change INTEGER NOT NULL DEFAULT 0")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_predicted_grade_notified INTEGER NOT NULL DEFAULT 1")
+ db.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_final_grade_notified INTEGER NOT NULL DEFAULT 1")
+ db.execSQL("ALTER TABLE GradesSummary ADD COLUMN predicted_grade_last_change INTEGER NOT NULL DEFAULT 0")
+ db.execSQL("ALTER TABLE GradesSummary ADD COLUMN final_grade_last_change INTEGER NOT NULL DEFAULT 0")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt
index 5c60beead..a7ba763df 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt
@@ -5,24 +5,25 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration27 : Migration(26, 27) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Students ADD COLUMN user_name TEXT NOT NULL DEFAULT \"\"")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Students ADD COLUMN user_name TEXT NOT NULL DEFAULT \"\"")
- val students = getStudentsIdsAndNames(database)
- val units = getReportingUnits(database)
+ val students = getStudentsIdsAndNames(db)
+ val units = getReportingUnits(db)
students.forEach { (id, userLoginId, studentName) ->
- val userNameFromUnits = units.singleOrNull { (senderId, _) -> senderId == userLoginId }?.second
+ val userNameFromUnits =
+ units.singleOrNull { (senderId, _) -> senderId == userLoginId }?.second
val normalizedStudentName = studentName.split(" ").asReversed().joinToString(" ")
val userName = userNameFromUnits ?: normalizedStudentName
- database.execSQL("UPDATE Students SET user_name = '$userName' WHERE id = '$id'")
+ db.execSQL("UPDATE Students SET user_name = '$userName' WHERE id = '$id'")
}
}
- private fun getStudentsIdsAndNames(database: SupportSQLiteDatabase): MutableList> {
+ private fun getStudentsIdsAndNames(db: SupportSQLiteDatabase): MutableList> {
val students = mutableListOf>()
- database.query("SELECT id, user_login_id, student_name FROM Students").use {
+ db.query("SELECT id, user_login_id, student_name FROM Students").use {
if (it.moveToFirst()) {
do {
students.add(Triple(it.getLong(0), it.getInt(1), it.getString(2)))
@@ -33,9 +34,9 @@ class Migration27 : Migration(26, 27) {
return students
}
- private fun getReportingUnits(database: SupportSQLiteDatabase): MutableList> {
+ private fun getReportingUnits(db: SupportSQLiteDatabase): MutableList> {
val units = mutableListOf>()
- database.query("SELECT sender_id, sender_name FROM ReportingUnits").use {
+ db.query("SELECT sender_id, sender_name FROM ReportingUnits").use {
if (it.moveToFirst()) {
do {
units.add(it.getInt(0) to it.getString(1))
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt
index 51e7628b5..e8a5a4a86 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration28 : Migration(27, 28) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Conferences (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt
index 327552d75..dac303d27 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt
@@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration29 : Migration(28, 29) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS GradesStatistics")
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS GradesStatistics")
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS GradeSemesterStatistics (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
@@ -16,8 +17,10 @@ class Migration29 : Migration(28, 29) {
amounts TEXT NOT NULL,
student_grade INTEGER NOT NULL
)
- """)
- database.execSQL("""
+ """
+ )
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS GradePartialStatistics (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt
index d9699c0f4..44d421648 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration3 : Migration(2, 3) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS CompletedLesson (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt
index b33914fec..3fea8ec0e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration30 : Migration(29, 30) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE TimetableAdditional (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
@@ -16,6 +17,7 @@ class Migration30 : Migration(29, 30) {
date INTEGER NOT NULL,
subject TEXT NOT NULL
)
- """)
+ """
+ )
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt
index 064a3e5bc..28fb10562 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt
@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration31 : Migration(30, 31) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL(
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
"""CREATE TABLE IF NOT EXISTS StudentInfo (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt
index 508485e08..347873936 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt
@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration32 : Migration(31, 32) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt
index 4a57880d4..9778d2790 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt
@@ -5,10 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration33 : Migration(32, 33) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS StudentInfo")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS StudentInfo")
- database.execSQL(
+ db.execSQL(
"""CREATE TABLE IF NOT EXISTS StudentInfo (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt
index 2c57eb00a..e9eec58cd 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt
@@ -5,9 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration34 : Migration(33, 34) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("DELETE FROM ReportingUnits")
- database.execSQL("DELETE FROM Recipients")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("DELETE FROM ReportingUnits")
+ db.execSQL("DELETE FROM Recipients")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt
index f63431d00..b238ce8b4 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt
@@ -7,13 +7,13 @@ import io.github.wulkanowy.utils.AppInfo
class Migration35(private val appInfo: AppInfo) : Migration(34, 35) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0")
- database.query("SELECT * FROM Students").use {
+ db.query("SELECT * FROM Students").use {
while (it.moveToNext()) {
val studentId = it.getLongOrNull(0)
- database.execSQL(
+ db.execSQL(
"""
UPDATE Students
SET avatar_color = ${appInfo.defaultColorsForAvatar.random()}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt
index 7ea106585..62ce346cd 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt
@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration36 : Migration(35, 36) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Exams ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
- database.execSQL("ALTER TABLE Homework ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Exams ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
+ db.execSQL("ALTER TABLE Homework ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt
index a3fcd51a6..9ab35514f 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt
@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration37 : Migration(36, 37) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL(
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS TimetableHeaders (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt
index 1f90f5a44..bb9b32bfa 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration38 : Migration(37, 38) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`student_id` INTEGER NOT NULL,
@@ -14,6 +15,7 @@ class Migration38 : Migration(37, 38) {
`subject` TEXT NOT NULL,
`content` TEXT NOT NULL
)
- """)
+ """
+ )
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt
index 6c0d36dd2..2e5315bf4 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt
@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration39 : Migration(38, 39) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Conferences ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
- database.execSQL("ALTER TABLE SchoolAnnouncements ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Conferences ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
+ db.execSQL("ALTER TABLE SchoolAnnouncements ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt
index 0ae89bdd6..b6089aa62 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt
@@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration4 : Migration(3, 4) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS Messages")
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS Messages")
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Messages (
id INTEGER PRIMARY KEY NOT NULL,
is_notified INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt
index 6d2795c7c..8e38b0c84 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt
@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration40 : Migration(39, 40) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL(
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Notifications` (
`student_id` INTEGER NOT NULL,
@@ -20,4 +20,4 @@ class Migration40 : Migration(39, 40) {
"""
)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt
index ccaf85755..bfc28334b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt
@@ -7,9 +7,9 @@ import io.github.wulkanowy.data.enums.GradeExpandMode
class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migration(40, 41) {
- override fun migrate(database: SupportSQLiteDatabase) {
+ override fun migrate(db: SupportSQLiteDatabase) {
migrateSharedPreferences()
- database.execSQL("ALTER TABLE Homework ADD COLUMN is_added_by_user INTEGER NOT NULL DEFAULT 0")
+ db.execSQL("ALTER TABLE Homework ADD COLUMN is_added_by_user INTEGER NOT NULL DEFAULT 0")
}
private fun migrateSharedPreferences() {
@@ -18,4 +18,4 @@ class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migratio
}
sharedPrefProvider.delete("pref_key_expand_grade")
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt
index 3d66f301b..14356e279 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt
@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration42 : Migration(41, 42) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL(
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
"""CREATE TABLE IF NOT EXISTS `AdminMessages` (
`id` INTEGER NOT NULL,
`title` TEXT NOT NULL,
@@ -21,4 +21,4 @@ class Migration42 : Migration(41, 42) {
PRIMARY KEY(`id`))"""
)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt
index 68c2834d6..ef8108166 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt
@@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration43 : Migration(42, 43) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Timetable ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
- database.execSQL("ALTER TABLE Attendance ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Timetable ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
+ db.execSQL("ALTER TABLE Attendance ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt
index 7bdcab5f4..0a4e5f962 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt
@@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration44 : Migration(43, 44) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE AdminMessages ADD COLUMN is_dismissible INTEGER NOT NULL DEFAULT 0")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE AdminMessages ADD COLUMN is_dismissible INTEGER NOT NULL DEFAULT 0")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt
index d3fa5cf93..0bacbaa0e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt
@@ -8,65 +8,65 @@ import java.time.ZoneOffset
class Migration46 : Migration(45, 46) {
- override fun migrate(database: SupportSQLiteDatabase) {
- migrateConferences(database)
- migrateMessages(database)
- migrateMobileDevices(database)
- migrateNotifications(database)
- migrateTimetable(database)
- migrateTimetableAdditional(database)
+ override fun migrate(db: SupportSQLiteDatabase) {
+ migrateConferences(db)
+ migrateMessages(db)
+ migrateMobileDevices(db)
+ migrateNotifications(db)
+ migrateTimetable(db)
+ migrateTimetableAdditional(db)
}
- private fun migrateConferences(database: SupportSQLiteDatabase) {
- database.query("SELECT * FROM Conferences").use {
+ private fun migrateConferences(db: SupportSQLiteDatabase) {
+ db.query("SELECT * FROM Conferences").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
- database.execSQL("UPDATE Conferences SET date = $timestampUtc WHERE id = $id")
+ db.execSQL("UPDATE Conferences SET date = $timestampUtc WHERE id = $id")
}
}
}
- private fun migrateMessages(database: SupportSQLiteDatabase) {
- database.query("SELECT * FROM Messages").use {
+ private fun migrateMessages(db: SupportSQLiteDatabase) {
+ db.query("SELECT * FROM Messages").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
- database.execSQL("UPDATE Messages SET date = $timestampUtc WHERE id = $id")
+ db.execSQL("UPDATE Messages SET date = $timestampUtc WHERE id = $id")
}
}
}
- private fun migrateMobileDevices(database: SupportSQLiteDatabase) {
- database.query("SELECT * FROM MobileDevices").use {
+ private fun migrateMobileDevices(db: SupportSQLiteDatabase) {
+ db.query("SELECT * FROM MobileDevices").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
- database.execSQL("UPDATE MobileDevices SET date = $timestampUtc WHERE id = $id")
+ db.execSQL("UPDATE MobileDevices SET date = $timestampUtc WHERE id = $id")
}
}
}
- private fun migrateNotifications(database: SupportSQLiteDatabase) {
- database.query("SELECT * FROM Notifications").use {
+ private fun migrateNotifications(db: SupportSQLiteDatabase) {
+ db.query("SELECT * FROM Notifications").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
- database.execSQL("UPDATE Notifications SET date = $timestampUtc WHERE id = $id")
+ db.execSQL("UPDATE Notifications SET date = $timestampUtc WHERE id = $id")
}
}
}
- private fun migrateTimetable(database: SupportSQLiteDatabase) {
- database.query("SELECT * FROM Timetable").use {
+ private fun migrateTimetable(db: SupportSQLiteDatabase) {
+ db.query("SELECT * FROM Timetable").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start"))
@@ -74,13 +74,13 @@ class Migration46 : Migration(45, 46) {
val timestampUtcStart = timestampLocalStart.timestampLocalToUTC()
val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC()
- database.execSQL("UPDATE Timetable SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
+ db.execSQL("UPDATE Timetable SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
}
}
}
- private fun migrateTimetableAdditional(database: SupportSQLiteDatabase) {
- database.query("SELECT * FROM TimetableAdditional").use {
+ private fun migrateTimetableAdditional(db: SupportSQLiteDatabase) {
+ db.query("SELECT * FROM TimetableAdditional").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start"))
@@ -88,7 +88,7 @@ class Migration46 : Migration(45, 46) {
val timestampUtcStart = timestampLocalStart.timestampLocalToUTC()
val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC()
- database.execSQL("UPDATE TimetableAdditional SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
+ db.execSQL("UPDATE TimetableAdditional SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt
index 6e1de19d4..97766c01e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt
@@ -5,10 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration49 : Migration(48, 49) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS SchoolAnnouncements")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS SchoolAnnouncements")
- database.execSQL(
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` (
`user_login_id` INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt
index dbcd916ba..a5b4e8e1a 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt
@@ -7,11 +7,16 @@ import java.time.ZoneOffset
class Migration5 : Migration(4, 5) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Students ADD COLUMN registration_date INTEGER DEFAULT 0 NOT NULL")
- database.execSQL("UPDATE Students SET registration_date = '${now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()}'")
- database.execSQL("DROP TABLE IF EXISTS Notes")
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Students ADD COLUMN registration_date INTEGER DEFAULT 0 NOT NULL")
+ db.execSQL(
+ "UPDATE Students SET registration_date = '${
+ now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()
+ }'"
+ )
+ db.execSQL("DROP TABLE IF EXISTS Notes")
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Notes (
id INTEGER PRIMARY KEY NOT NULL,
is_read INTEGER NOT NULL,
@@ -21,6 +26,7 @@ class Migration5 : Migration(4, 5) {
teacher TEXT NOT NULL,
category TEXT NOT NULL,
content TEXT NOT NULL)
- """)
+ """
+ )
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt
index d45a81570..577998ca0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt
@@ -5,9 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration50 : Migration(49, 50) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS MobileDevices")
- database.execSQL(
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS MobileDevices")
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `MobileDevices` (
`user_login_id` INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt
index e78e2e3a7..7023049f9 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt
@@ -5,17 +5,17 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration51 : Migration(50, 51) {
- override fun migrate(database: SupportSQLiteDatabase) {
- createMailboxTable(database)
- recreateMessagesTable(database)
- recreateMessageAttachmentsTable(database)
- recreateRecipientsTable(database)
- deleteReportingUnitTable(database)
+ override fun migrate(db: SupportSQLiteDatabase) {
+ createMailboxTable(db)
+ recreateMessagesTable(db)
+ recreateMessageAttachmentsTable(db)
+ recreateRecipientsTable(db)
+ deleteReportingUnitTable(db)
}
- private fun createMailboxTable(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS Mailboxes")
- database.execSQL(
+ private fun createMailboxTable(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS Mailboxes")
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Mailboxes` (
`globalKey` TEXT NOT NULL,
@@ -30,9 +30,9 @@ class Migration51 : Migration(50, 51) {
)
}
- private fun recreateMessagesTable(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS Messages")
- database.execSQL(
+ private fun recreateMessagesTable(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS Messages")
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Messages` (
`message_global_key` TEXT NOT NULL,
@@ -52,9 +52,9 @@ class Migration51 : Migration(50, 51) {
)
}
- private fun recreateMessageAttachmentsTable(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS MessageAttachments")
- database.execSQL(
+ private fun recreateMessageAttachmentsTable(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS MessageAttachments")
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `MessageAttachments` (
`real_id` INTEGER NOT NULL,
@@ -66,9 +66,9 @@ class Migration51 : Migration(50, 51) {
)
}
- private fun recreateRecipientsTable(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS Recipients")
- database.execSQL(
+ private fun recreateRecipientsTable(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS Recipients")
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Recipients` (
`mailboxGlobalKey` TEXT NOT NULL,
@@ -82,7 +82,7 @@ class Migration51 : Migration(50, 51) {
)
}
- private fun deleteReportingUnitTable(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS ReportingUnits")
+ private fun deleteReportingUnitTable(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS ReportingUnits")
}
}
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
index 12624a51a..dd9e68c97 100644
--- 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
@@ -5,14 +5,14 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration53 : Migration(52, 53) {
- override fun migrate(database: SupportSQLiteDatabase) {
- createMailboxTable(database)
- recreateMessagesTable(database)
+ override fun migrate(db: SupportSQLiteDatabase) {
+ createMailboxTable(db)
+ recreateMessagesTable(db)
}
- private fun createMailboxTable(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS Mailboxes")
- database.execSQL(
+ private fun createMailboxTable(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS Mailboxes")
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Mailboxes` (
`globalKey` TEXT NOT NULL,
@@ -29,9 +29,9 @@ class Migration53 : Migration(52, 53) {
)
}
- private fun recreateMessagesTable(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS Messages")
- database.execSQL(
+ private fun recreateMessagesTable(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS Messages")
+ db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Messages` (
`email` TEXT NOT NULL,
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
index 678bd32f2..60bd21f0a 100644
--- 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
@@ -5,22 +5,24 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration54 : Migration(53, 54) {
- override fun migrate(database: SupportSQLiteDatabase) {
- migrateResman(database)
- removeTomaszowMazowieckiStudents(database)
+ override fun migrate(db: SupportSQLiteDatabase) {
+ migrateResman(db)
+ removeTomaszowMazowieckiStudents(db)
}
- private fun migrateResman(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ private fun migrateResman(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
UPDATE Students SET
scrapper_base_url = 'https://vulcan.net.pl',
login_type = 'ADFSLightScoped',
symbol = 'rzeszowprojekt'
WHERE scrapper_base_url = 'https://resman.pl'
- """.trimIndent())
+ """.trimIndent()
+ )
}
- private fun removeTomaszowMazowieckiStudents(database: SupportSQLiteDatabase) {
- database.execSQL("DELETE FROM Students WHERE symbol = 'tomaszowmazowiecki'")
+ private fun removeTomaszowMazowieckiStudents(db: SupportSQLiteDatabase) {
+ db.execSQL("DELETE FROM Students WHERE symbol = 'tomaszowmazowiecki'")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt
new file mode 100644
index 000000000..c440d58d6
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt
@@ -0,0 +1,10 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.DeleteColumn
+import androidx.room.migration.AutoMigrationSpec
+
+@DeleteColumn(
+ tableName = "AdminMessages",
+ columnName = "is_dismissible",
+)
+class Migration58 : AutoMigrationSpec
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt
index fa9436187..06cd5f0fd 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration6 : Migration(5, 6) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS ReportingUnits (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
@@ -15,9 +16,11 @@ class Migration6 : Migration(5, 6) {
sender_id INTEGER NOT NULL,
sender_name TEXT NOT NULL,
roles TEXT NOT NULL)
- """)
+ """
+ )
- database.execSQL("""
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Recipients (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
@@ -28,10 +31,11 @@ class Migration6 : Migration(5, 6) {
unit_id INTEGER NOT NULL,
role INTEGER NOT NULL,
hash TEXT NOT NULL)
- """)
+ """
+ )
- database.execSQL("DELETE FROM Semesters WHERE 1")
- database.execSQL("ALTER TABLE Semesters ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL")
- database.execSQL("ALTER TABLE Semesters ADD COLUMN unit_id INTEGER DEFAULT 0 NOT NULL")
+ db.execSQL("DELETE FROM Semesters WHERE 1")
+ db.execSQL("ALTER TABLE Semesters ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL")
+ db.execSQL("ALTER TABLE Semesters ADD COLUMN unit_id INTEGER DEFAULT 0 NOT NULL")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt
index 120716c81..83a822f22 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt
@@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration7 : Migration(6, 7) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS GradesStatistics (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
@@ -15,6 +16,7 @@ class Migration7 : Migration(6, 7) {
grade INTEGER NOT NULL,
amount INTEGER NOT NULL,
is_semester INTEGER NOT NULL)
- """)
+ """
+ )
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt
index 7009ee129..992e8c68d 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt
@@ -5,9 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration8 : Migration(7, 8) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE Timetable ADD COLUMN subjectOld TEXT DEFAULT \"\" NOT NULL")
- database.execSQL("ALTER TABLE Timetable ADD COLUMN roomOld TEXT DEFAULT \"\" NOT NULL")
- database.execSQL("ALTER TABLE Timetable ADD COLUMN teacherOld TEXT DEFAULT \"\" NOT NULL")
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Timetable ADD COLUMN subjectOld TEXT DEFAULT \"\" NOT NULL")
+ db.execSQL("ALTER TABLE Timetable ADD COLUMN roomOld TEXT DEFAULT \"\" NOT NULL")
+ db.execSQL("ALTER TABLE Timetable ADD COLUMN teacherOld TEXT DEFAULT \"\" NOT NULL")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt
index d79a57062..b83c34c41 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt
@@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration9 : Migration(8, 9) {
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("DROP TABLE IF EXISTS Messages")
- database.execSQL("""
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("DROP TABLE IF EXISTS Messages")
+ db.execSQL(
+ """
CREATE TABLE IF NOT EXISTS Messages (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt
index 899ba9085..7cb4202a1 100644
--- a/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt
@@ -3,5 +3,10 @@ package io.github.wulkanowy.data.enums
enum class MessageFolder(val id: Int = 1) {
RECEIVED(1),
SENT(2),
- TRASHED(3)
+ TRASHED(3),
+ ;
+
+ companion object {
+ fun byId(id: Int) = entries.first { it.id == id }
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt
index 178de682a..66e922171 100644
--- a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt
@@ -1,10 +1,12 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
-import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade
+import io.github.wulkanowy.sdk.pojo.GradeDescriptive as SdkGradeDescriptive
+import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
fun List.mapToEntities(semester: Semester) = map {
Grade(
@@ -40,3 +42,15 @@ fun List.mapToEntities(semester: Semester) = map {
average = it.average
)
}
+
+@JvmName("mapGradeDescriptiveToEntities")
+fun List.mapToEntities(semester: Semester) = map {
+ GradeDescriptive(
+ semesterId = semester.semesterId,
+ studentId = semester.studentId,
+ subject = it.subject,
+ description = it.description
+ )
+}
+
+
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 3afb99077..2d817d8a4 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
@@ -9,11 +9,15 @@ import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent
-import io.github.wulkanowy.utils.*
-import kotlinx.coroutines.Dispatchers
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.switchSemester
+import io.github.wulkanowy.utils.uniqueSubtract
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
@@ -52,13 +56,11 @@ 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
- )
- }
+ val lessons = timetableDb.load(
+ semester.diaryId, semester.studentId, start.monday, end.sunday
+ )
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getAttendance(start.monday, end.sunday)
.mapToEntities(semester, lessons)
},
@@ -86,9 +88,12 @@ class AttendanceRepository @Inject constructor(
return attendanceDb.updateAll(timetable)
}
+ @JvmName("excuseForAbsenceLessons")
suspend fun excuseForAbsence(
- student: Student, semester: Semester,
- absenceList: List, reason: String? = null
+ student: Student,
+ semester: Semester,
+ absenceList: List,
+ reason: String? = null
) {
val items = absenceList.map { attendance ->
Absent(
@@ -97,7 +102,25 @@ class AttendanceRepository @Inject constructor(
)
}
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
+ .excuseForAbsence(items, reason)
+ }
+
+ @JvmName("excuseForAbsenceDays")
+ suspend fun excuseForAbsence(
+ student: Student,
+ semester: Semester,
+ days: List,
+ reason: String? = null
+ ) {
+ val items = days.map { day ->
+ Absent(
+ date = LocalDateTime.of(day, LocalTime.of(0, 0)),
+ timeId = null,
+ )
+ }
+ sdk.init(student)
+ .switchSemester(semester)
.excuseForAbsence(items, reason)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt
index 8e0709135..6bdcf9d7f 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt
@@ -9,6 +9,7 @@ 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.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@@ -40,7 +41,7 @@ class AttendanceSummaryRepository @Inject constructor(
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getAttendanceSummary(subjectId)
.mapToEntities(semester, subjectId)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt
index 8f393cadb..1579ae62b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt
@@ -48,7 +48,7 @@ class CompletedLessonsRepository @Inject constructor(
},
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getCompletedLessons(start.monday, end.sunday)
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt
index 83204cab0..7eb37f0b7 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt
@@ -10,6 +10,7 @@ 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.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
@@ -46,7 +47,7 @@ class ConferenceRepository @Inject constructor(
},
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getConferences()
.mapToEntities(semester)
.filter { it.date >= startDate }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt
index 013c0951d..96026a55b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt
@@ -7,7 +7,13 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.endExamsDay
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.startExamsDay
+import io.github.wulkanowy.utils.switchSemester
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
@@ -51,7 +57,7 @@ class ExamRepository @Inject constructor(
},
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getExams(start.startExamsDay, start.endExamsDay)
.mapToEntities(semester)
},
@@ -67,14 +73,16 @@ class ExamRepository @Inject constructor(
filterResult = { it.filter { item -> item.date in start..end } }
)
- fun getExamsFromDatabase(semester: Semester, start: LocalDate): Flow> {
- return examDb.loadAll(
- diaryId = semester.diaryId,
- studentId = semester.studentId,
- from = start.startExamsDay,
- end = start.endExamsDay
- )
- }
+ fun getExamsFromDatabase(
+ semester: Semester,
+ start: LocalDate,
+ end: LocalDate
+ ): Flow> = examDb.loadAll(
+ diaryId = semester.diaryId,
+ studentId = semester.studentId,
+ from = start,
+ end = end,
+ )
suspend fun updateExam(exam: List) = examDb.updateAll(exam)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
index f5f895d82..1e2ea9354 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
@@ -1,15 +1,22 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.GradeDao
+import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
+import io.github.wulkanowy.utils.toLocalDate
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
@@ -22,14 +29,13 @@ import javax.inject.Singleton
class GradeRepository @Inject constructor(
private val gradeDb: GradeDao,
private val gradeSummaryDb: GradeSummaryDao,
+ private val gradeDescriptiveDb: GradeDescriptiveDao,
private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper,
) {
private val saveFetchResultMutex = Mutex()
- private val cacheKey = "grade"
-
fun getGrades(
student: Student,
semester: Semester,
@@ -41,30 +47,52 @@ class GradeRepository @Inject constructor(
//When details is empty and summary is not, app will not use summary cache - edge case
it.first.isEmpty()
},
- shouldFetch = { (details, summaries) ->
- val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
- details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
+ shouldFetch = { (details, summaries, descriptive) ->
+ val isExpired =
+ refreshHelper.shouldBeRefreshed(getRefreshKey(GRADE_CACHE_KEY, semester))
+ details.isEmpty() || (summaries.isEmpty() && descriptive.isEmpty()) || forceRefresh || isExpired
},
query = {
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
- detailsFlow.combine(summaryFlow) { details, summaries -> details to summaries }
+ val descriptiveFlow =
+ gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
+
+ combine(detailsFlow, summaryFlow, descriptiveFlow) { details, summaries, descriptive ->
+ Triple(details, summaries, descriptive)
+ }
},
fetch = {
- val (details, summary) = sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ val (details, summary, descriptive) = sdk.init(student)
+ .switchSemester(semester)
.getGrades(semester.semesterId)
- details.mapToEntities(semester) to summary.mapToEntities(semester)
+ Triple(
+ details.mapToEntities(semester),
+ summary.mapToEntities(semester),
+ descriptive.mapToEntities(semester)
+ )
},
- saveFetchResult = { (oldDetails, oldSummary), (newDetails, newSummary) ->
+ saveFetchResult = { (oldDetails, oldSummary, oldDescriptive), (newDetails, newSummary, newDescriptive) ->
refreshGradeDetails(student, oldDetails, newDetails, notify)
refreshGradeSummaries(oldSummary, newSummary, notify)
+ refreshGradeDescriptions(oldDescriptive, newDescriptive, notify)
- refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
+ refreshHelper.updateLastRefreshTimestamp(getRefreshKey(GRADE_CACHE_KEY, semester))
}
)
+ private suspend fun refreshGradeDescriptions(
+ old: List,
+ new: List,
+ notify: Boolean
+ ) {
+ gradeDescriptiveDb.deleteAll(old uniqueSubtract new)
+ gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach {
+ if (notify) it.isNotified = false
+ })
+ }
+
private suspend fun refreshGradeDetails(
student: Student,
oldGrades: List,
@@ -132,6 +160,10 @@ class GradeRepository @Inject constructor(
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
}
+ fun getGradesDescriptiveFromDatabase(semester: Semester): Flow> {
+ return gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
+ }
+
suspend fun updateGrade(grade: Grade) {
return gradeDb.updateAll(listOf(grade))
}
@@ -143,4 +175,13 @@ class GradeRepository @Inject constructor(
suspend fun updateGradesSummary(gradesSummary: List) {
return gradeSummaryDb.updateAll(gradesSummary)
}
+
+ suspend fun updateGradesDescriptive(gradesDescriptive: List) {
+ return gradeDescriptiveDb.updateAll(gradesDescriptive)
+ }
+
+ private companion object {
+
+ private const val GRADE_CACHE_KEY = "grade"
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt
index 9fa06c497..23d7b8582 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt
@@ -16,6 +16,7 @@ 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.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import java.util.*
@@ -56,7 +57,7 @@ class GradeStatisticsRepository @Inject constructor(
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getGradesPartialStatistics(semester.semesterId)
.mapToEntities(semester)
},
@@ -101,7 +102,7 @@ class GradeStatisticsRepository @Inject constructor(
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getGradesSemesterStatistics(semester.semesterId)
.mapToEntities(semester)
},
@@ -157,7 +158,7 @@ class GradeStatisticsRepository @Inject constructor(
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getGradesPointsStatistics(semester.semesterId)
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
index f564824de..010cf8458 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
@@ -7,7 +7,13 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.switchSemester
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
import javax.inject.Inject
@@ -50,7 +56,7 @@ class HomeworkRepository @Inject constructor(
},
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getHomework(start.monday, end.sunday)
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt
index 87e8410f1..4ff4517d0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt
@@ -35,12 +35,15 @@ class LuckyNumberRepository @Inject constructor(
fetch = {
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
},
- saveFetchResult = { old, new ->
- if (new != old) {
- old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
- luckyNumberDb.insertAll(listOfNotNull((new?.apply {
- if (notify) isNotified = false
- })))
+ saveFetchResult = { oldLuckyNumber, newLuckyNumber ->
+ newLuckyNumber ?: return@networkBoundResource
+
+ if (newLuckyNumber != oldLuckyNumber) {
+ val updatedLuckNumberList =
+ listOf(newLuckyNumber.apply { if (notify) isNotified = false })
+
+ oldLuckyNumber?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
+ luckyNumberDb.insertAll(updatedLuckNumberList)
}
}
)
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 c8fccb23d..96f048706 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
@@ -8,13 +8,17 @@ 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.dao.MutedMessageSendersDao
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
+import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
+import io.github.wulkanowy.data.db.entities.MutedMessageSender
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
+import io.github.wulkanowy.data.enums.MessageFolder.SENT
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities
@@ -22,6 +26,7 @@ import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.pojos.MessageDraft
+import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk
@@ -31,7 +36,6 @@ import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@@ -42,6 +46,7 @@ import javax.inject.Singleton
@Singleton
class MessageRepository @Inject constructor(
private val messagesDb: MessagesDao,
+ private val mutedMessageSendersDao: MutedMessageSendersDao,
private val messageAttachmentDao: MessageAttachmentDao,
private val sdk: Sdk,
@ApplicationContext private val context: Context,
@@ -51,7 +56,6 @@ class MessageRepository @Inject constructor(
private val mailboxDao: MailboxDao,
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
) {
-
private val saveFetchResultMutex = Mutex()
private val messagesCacheKey = "message"
@@ -63,7 +67,7 @@ class MessageRepository @Inject constructor(
folder: MessageFolder,
forceRefresh: Boolean,
notify: Boolean = false,
- ): Flow>> = networkBoundResource(
+ ): Flow>> = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
@@ -74,8 +78,8 @@ class MessageRepository @Inject constructor(
},
query = {
if (mailbox == null) {
- messagesDb.loadAll(folder.id, student.email)
- } else messagesDb.loadAll(mailbox.globalKey, folder.id)
+ messagesDb.loadMessagesWithMutedAuthor(folder.id, student.email)
+ } else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id)
},
fetch = {
sdk.init(student).getMessages(
@@ -83,10 +87,12 @@ class MessageRepository @Inject constructor(
mailboxKey = mailbox?.globalKey,
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
},
- saveFetchResult = { old, new ->
+ saveFetchResult = { oldWithAuthors, new ->
+ val old = oldWithAuthors.map { it.message }
messagesDb.deleteAll(old uniqueSubtract new)
messagesDb.insertAll((new uniqueSubtract old).onEach {
- it.isNotified = !notify
+ val muted = isMuted(it.correspondents)
+ it.isNotified = !notify || muted
})
refreshHelper.updateLastRefreshTimestamp(
@@ -106,9 +112,7 @@ class MessageRepository @Inject constructor(
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
(it.message.unread && markAsRead) || it.message.content.isBlank()
},
- query = {
- messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
- },
+ query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
fetch = {
sdk.init(student).getMessageDetails(
messageKey = it!!.message.messageGlobalKey,
@@ -152,17 +156,30 @@ class MessageRepository @Inject constructor(
subject: String,
content: String,
recipients: List,
- mailboxId: String,
+ mailbox: Mailbox,
) {
sdk.init(student).sendMessage(
subject = subject,
content = content,
recipients = recipients.mapFromEntities(),
- mailboxId = mailboxId,
+ mailboxId = mailbox.globalKey,
)
+ refreshFolders(student, mailbox, listOf(SENT))
}
- suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List) {
+ suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List) {
+ sdk.init(student).restoreMessages(
+ messages = messages.map { it.messageGlobalKey },
+ )
+
+ refreshFolders(student, mailbox)
+ }
+
+ suspend fun deleteMessage(student: Student, message: Message) {
+ deleteMessages(student, listOf(message))
+ }
+
+ suspend fun deleteMessages(student: Student, messages: List) {
val firstMessage = messages.first()
sdk.init(student).deleteMessages(
messages = messages.map { it.messageGlobalKey },
@@ -181,18 +198,24 @@ class MessageRepository @Inject constructor(
}
messagesDb.updateAll(deletedMessages)
- } else messagesDb.deleteAll(messages)
-
- getMessages(
- student = student,
- mailbox = mailbox,
- folder = TRASHED,
- forceRefresh = true,
- ).first()
+ } else {
+ messagesDb.deleteAll(messages)
+ }
}
- suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
- deleteMessages(student, mailbox, listOf(message))
+ private suspend fun refreshFolders(
+ student: Student,
+ mailbox: Mailbox?,
+ folders: List = MessageFolder.entries
+ ) {
+ folders.forEach {
+ getMessages(
+ student = student,
+ mailbox = mailbox,
+ folder = it,
+ forceRefresh = true,
+ ).toFirstResult()
+ }
}
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
@@ -236,4 +259,18 @@ class MessageRepository @Inject constructor(
context.getString(R.string.pref_key_message_draft),
value?.let { json.encodeToString(it) }
)
+
+ private suspend fun isMuted(author: String): Boolean {
+ return mutedMessageSendersDao.checkMute(author)
+ }
+
+ suspend fun muteMessage(author: String) {
+ if (isMuted(author)) return
+ mutedMessageSendersDao.insertMute(MutedMessageSender(author))
+ }
+
+ suspend fun unmuteMessage(author: String) {
+ if (!isMuted(author)) return
+ mutedMessageSendersDao.deleteMute(author)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt
index 07c6959e3..412f9e7f0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt
@@ -12,6 +12,7 @@ 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.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@@ -42,7 +43,7 @@ class MobileDeviceRepository @Inject constructor(
query = { mobileDb.loadAll(student.userLoginId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getRegisteredDevices()
.mapToEntities(student)
},
@@ -56,7 +57,7 @@ class MobileDeviceRepository @Inject constructor(
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.unregisterDevice(device.deviceId)
mobileDb.deleteAll(listOf(device))
@@ -64,7 +65,7 @@ class MobileDeviceRepository @Inject constructor(
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
return sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getToken()
.mapToMobileDeviceToken()
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt
index 4101803f3..eeb1d53ef 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt
@@ -41,7 +41,7 @@ class NoteRepository @Inject constructor(
query = { noteDb.loadAll(student.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getNotes()
.mapToEntities(semester)
},
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 495415f9f..64e60a60b 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
@@ -9,7 +9,12 @@ import com.fredporciuncula.flow.preferences.Preference
import com.fredporciuncula.flow.preferences.Serializer
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.enums.*
+import io.github.wulkanowy.data.enums.AppTheme
+import io.github.wulkanowy.data.enums.GradeColorTheme
+import io.github.wulkanowy.data.enums.GradeExpandMode
+import io.github.wulkanowy.data.enums.GradeSortingMode
+import io.github.wulkanowy.data.enums.TimetableGapsMode
+import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
@@ -18,7 +23,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.Instant
-import java.util.*
+import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
@@ -303,19 +308,6 @@ class PreferencesRepository @Inject constructor(
get() = sharedPref.getBoolean(PREF_KEY_APP_SUPPORT_SHOWN, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_APP_SUPPORT_SHOWN, value) }
- var isAgreeToProcessData: Boolean
- get() = getBoolean(
- R.string.pref_key_ads_consent_data_processing,
- R.bool.pref_default_ads_consent_data_processing
- )
- set(value) = sharedPref.edit {
- putBoolean(context.getString(R.string.pref_key_ads_consent_data_processing), value)
- }
-
- var isPersonalizedAdsEnabled: Boolean
- get() = sharedPref.getBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, false)
- set(value) = sharedPref.edit { putBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, value) }
-
val isAdsEnabledFlow = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_ads_enabled),
context.resources.getBoolean(R.bool.pref_default_ads_enabled)
@@ -398,7 +390,6 @@ class PreferencesRepository @Inject constructor(
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/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt
index 7972ed084..f757ef047 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt
@@ -9,6 +9,7 @@ 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.switchSemester
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@@ -40,7 +41,7 @@ class SchoolRepository @Inject constructor(
query = { schoolDb.load(semester.studentId, semester.classId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getSchool()
.mapToEntity(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt
index 9c6429343..216a8c112 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt
@@ -11,6 +11,7 @@ import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.utils.IntegrityHelper
import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.withTimeout
import timber.log.Timber
import java.util.UUID
@@ -42,11 +43,7 @@ class SchoolsRepository @Inject constructor(
val schoolInfo = sdk
.init(student.copy(password = loginData.password))
- .switchDiary(
- diaryId = semester.diaryId,
- kindergartenDiaryId = semester.kindergartenDiaryId,
- schoolYear = semester.schoolYear
- )
+ .switchSemester(semester)
.getSchool()
schoolsService.logLoginEvent(
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt
index efc82a772..d6cd25c82 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt
@@ -7,6 +7,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@@ -30,7 +31,7 @@ class StudentInfoRepository @Inject constructor(
query = { studentInfoDao.loadStudentInfo(student.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getStudentInfo().mapToEntity(semester)
},
saveFetchResult = { old, new ->
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 2e04224aa..e063840cb 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
@@ -1,8 +1,6 @@
package io.github.wulkanowy.data.repositories
-import android.content.Context
import androidx.room.withTransaction
-import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
@@ -17,20 +15,20 @@ import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.init
-import io.github.wulkanowy.utils.security.decrypt
-import io.github.wulkanowy.utils.security.encrypt
+import io.github.wulkanowy.utils.security.Scrambler
+import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class StudentRepository @Inject constructor(
- @ApplicationContext private val context: Context,
private val dispatchers: DispatchersProvider,
private val studentDb: StudentDao,
private val semesterDb: SemesterDao,
private val sdk: Sdk,
- private val appDatabase: AppDatabase
+ private val appDatabase: AppDatabase,
+ private val scrambler: Scrambler,
) {
suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false
@@ -68,7 +66,7 @@ class StudentRepository @Inject constructor(
student = student.apply {
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
student.password = withContext(dispatchers.io) {
- decrypt(student.password)
+ scrambler.decrypt(student.password)
}
}
},
@@ -86,7 +84,7 @@ class StudentRepository @Inject constructor(
}.apply {
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
student.password = withContext(dispatchers.io) {
- decrypt(student.password)
+ scrambler.decrypt(student.password)
}
}
}
@@ -96,7 +94,7 @@ class StudentRepository @Inject constructor(
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
student.password = withContext(dispatchers.io) {
- decrypt(student.password)
+ scrambler.decrypt(student.password)
}
}
return student
@@ -107,7 +105,7 @@ class StudentRepository @Inject constructor(
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
student.password = withContext(dispatchers.io) {
- decrypt(student.password)
+ scrambler.decrypt(student.password)
}
}
return student
@@ -120,7 +118,7 @@ class StudentRepository @Inject constructor(
it.apply {
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.HEBE) {
password = withContext(dispatchers.io) {
- encrypt(password, context)
+ scrambler.encrypt(password)
}
}
}
@@ -152,12 +150,12 @@ class StudentRepository @Inject constructor(
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.authorizePermission(pesel)
suspend fun refreshStudentName(student: Student, semester: Semester) {
val newCurrentApiStudent = sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getCurrentStudent() ?: return
val studentName = StudentName(
@@ -166,4 +164,15 @@ class StudentRepository @Inject constructor(
studentDb.update(studentName)
}
+
+ suspend fun deleteStudentsAssociatedWithAccount(student: Student) {
+ studentDb.deleteByEmailAndUserName(student.email, student.userName)
+ }
+
+ suspend fun clearAll() {
+ withContext(dispatchers.io) {
+ scrambler.clearKeyPair()
+ appDatabase.clearAllTables()
+ }
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt
index 3926122b3..98cb181af 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt
@@ -9,6 +9,7 @@ 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.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@@ -39,8 +40,9 @@ class SubjectRepository @Inject constructor(
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
- .getSubjects().mapToEntities(semester)
+ .switchSemester(semester)
+ .getSubjects()
+ .mapToEntities(semester)
},
saveFetchResult = { old, new ->
subjectDao.deleteAll(old uniqueSubtract new)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt
index 4e3b40f96..42698f922 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt
@@ -9,6 +9,7 @@ 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.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@@ -39,7 +40,7 @@ class TeacherRepository @Inject constructor(
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
fetch = {
sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getTeachers()
.mapToEntities(semester)
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
index 136fb8d5b..acbd02d18 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
@@ -3,13 +3,23 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
-import io.github.wulkanowy.data.db.entities.*
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.Timetable
+import io.github.wulkanowy.data.db.entities.TimetableAdditional
+import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.switchSemester
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.sync.Mutex
@@ -65,7 +75,7 @@ class TimetableRepository @Inject constructor(
query = { getFullTimetableFromDatabase(student, semester, start, end) },
fetch = {
val timetableFull = sdk.init(student)
- .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
+ .switchSemester(semester)
.getTimetable(start.monday, end.sunday)
timetableFull.mapToEntities(semester)
@@ -121,12 +131,12 @@ class TimetableRepository @Inject constructor(
}
}
- fun getTimetableFromDatabase(
+ suspend fun getTimetableFromDatabase(
semester: Semester,
- from: LocalDate,
+ start: LocalDate,
end: LocalDate
- ): Flow> {
- return timetableDb.loadAll(semester.diaryId, semester.studentId, from, end)
+ ): List {
+ return timetableDb.load(semester.diaryId, semester.studentId, start, end)
}
suspend fun updateTimetable(timetable: List) {
diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt
new file mode 100644
index 000000000..ffd005740
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt
@@ -0,0 +1,26 @@
+package io.github.wulkanowy.domain.timetable
+
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.repositories.TimetableRepository
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.sunday
+import java.time.LocalDate
+import javax.inject.Inject
+
+class IsStudentHasLessonsOnWeekendUseCase @Inject constructor(
+ private val timetableRepository: TimetableRepository,
+ private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase,
+) {
+
+ suspend operator fun invoke(
+ semester: Semester,
+ currentDate: LocalDate = LocalDate.now(),
+ ): Boolean {
+ val lessons = timetableRepository.getTimetableFromDatabase(
+ semester = semester,
+ start = currentDate.monday,
+ end = currentDate.sunday,
+ )
+ return isWeekendHasLessonsUseCase(lessons)
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt
new file mode 100644
index 000000000..908c9df78
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt
@@ -0,0 +1,17 @@
+package io.github.wulkanowy.domain.timetable
+
+import io.github.wulkanowy.data.db.entities.Timetable
+import java.time.DayOfWeek
+import javax.inject.Inject
+
+class IsWeekendHasLessonsUseCase @Inject constructor() {
+
+ operator fun invoke(
+ lessons: List,
+ ): Boolean = lessons.any {
+ it.date.dayOfWeek in listOf(
+ DayOfWeek.SATURDAY,
+ DayOfWeek.SUNDAY,
+ )
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
index 42078d03f..aae7882f1 100644
--- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
@@ -65,8 +65,6 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
range = lesson.start..lesson.end,
requestCode = getRequestCode(lesson.start, studentId)
)
-
- Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt
index 9b49ed178..7f90bbddc 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt
@@ -4,12 +4,12 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
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
@@ -88,4 +88,28 @@ class NewGradeNotification @Inject constructor(
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
}
+
+ suspend fun notifyDescriptive(items: List, student: Student) {
+ val notificationDataList = items.map {
+ NotificationData(
+ title = context.getPlural(R.plurals.grade_new_items_descriptive, 1),
+ content = "${it.subject}: ${it.description}",
+ destination = Destination.Grade,
+ )
+ }
+
+ val groupNotificationData = GroupNotificationData(
+ notificationDataList = notificationDataList,
+ title = context.getPlural(R.plurals.grade_new_items_descriptive, items.size),
+ content = context.getPlural(
+ R.plurals.grade_notify_new_items_descriptive,
+ items.size,
+ items.size
+ ),
+ destination = Destination.Grade,
+ type = NotificationType.NEW_GRADE_DESCRIPTIVE
+ )
+
+ appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt
index 023ae2e4c..4e7f27351 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt
@@ -37,6 +37,10 @@ enum class NotificationType(
channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade,
),
+ NEW_GRADE_DESCRIPTIVE(
+ channel = NewGradesChannel.CHANNEL_ID,
+ icon = R.drawable.ic_stat_grade,
+ ),
NEW_HOMEWORK(
channel = NewHomeworkChannel.CHANNEL_ID,
icon = R.drawable.ic_more_homework,
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt
index 657f69638..4fc097492 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt
@@ -16,17 +16,24 @@ class AttendanceWork @Inject constructor(
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
+ val startDate = now().previousOrSameSchoolDay
+ val endDate = startDate.plusDays(7)
+
attendanceRepository.getAttendance(
student = student,
semester = semester,
- start = now().previousOrSameSchoolDay,
- end = now().previousOrSameSchoolDay,
+ start = startDate,
+ end = endDate,
forceRefresh = true,
notify = notify,
)
.waitForResult()
- attendanceRepository.getAttendanceFromDatabase(semester, now().minusDays(7), now())
+ attendanceRepository.getAttendanceFromDatabase(
+ semester = semester,
+ start = startDate,
+ end = endDate,
+ )
.first()
.filterNot { it.isNotified }
.let {
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt
index 7071bce20..4b0b1bdb3 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt
@@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.NewExamNotification
+import io.github.wulkanowy.utils.endExamsDay
+import io.github.wulkanowy.utils.startExamsDay
import kotlinx.coroutines.flow.first
import java.time.LocalDate.now
import javax.inject.Inject
@@ -15,16 +17,24 @@ class ExamWork @Inject constructor(
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
+ val startDate = now().startExamsDay
+ val endDate = startDate.endExamsDay
+
examRepository.getExams(
student = student,
semester = semester,
- start = now(),
- end = now(),
+ start = startDate,
+ end = endDate,
forceRefresh = true,
notify = notify,
).waitForResult()
- examRepository.getExamsFromDatabase(semester, now()).first()
+ examRepository.getExamsFromDatabase(
+ semester = semester,
+ start = startDate,
+ end = endDate,
+ )
+ .first()
.filter { !it.isNotified }.let {
if (it.isNotEmpty()) newExamNotification.notify(it, student)
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt
index ba21b8600..b62ad94b9 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt
@@ -45,5 +45,15 @@ class GradeWork @Inject constructor(
grade.isFinalGradeNotified = true
})
}
+
+ gradeRepository.getGradesDescriptiveFromDatabase(semester).first()
+ .filter { !it.isNotified }
+ .let {
+ if (it.isNotEmpty()) newGradeNotification.notifyDescriptive(it, student)
+
+ gradeRepository.updateGradesDescriptive(it.onEach { grade ->
+ grade.isNotified = true
+ })
+ }
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt
index 4cfe27d0d..ddff3af7c 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt
@@ -5,7 +5,9 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
+import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.nextOrSameSchoolDay
+import io.github.wulkanowy.utils.sunday
import kotlinx.coroutines.flow.first
import java.time.LocalDate.now
import javax.inject.Inject
@@ -16,16 +18,24 @@ class HomeworkWork @Inject constructor(
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
+ val startDate = now().nextOrSameSchoolDay.monday
+ val endDate = startDate.sunday
+
homeworkRepository.getHomework(
student = student,
semester = semester,
- start = now().nextOrSameSchoolDay,
- end = now().nextOrSameSchoolDay,
+ start = startDate,
+ end = endDate,
forceRefresh = true,
notify = notify,
).waitForResult()
- homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first()
+ homeworkRepository.getHomeworkFromDatabase(
+ semester = semester,
+ start = startDate,
+ end = endDate
+ )
+ .first()
.filter { !it.isNotified }.let {
if (it.isNotEmpty()) newHomeworkNotification.notify(it, student)
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
index 29b1f13c7..2d10d925c 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
@@ -6,7 +6,6 @@ import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
import io.github.wulkanowy.utils.nextOrSameSchoolDay
-import kotlinx.coroutines.flow.first
import java.time.LocalDate.now
import javax.inject.Inject
@@ -16,18 +15,24 @@ class TimetableWork @Inject constructor(
) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
+ val startDate = now().nextOrSameSchoolDay
+ val endDate = startDate.plusDays(7)
+
timetableRepository.getTimetable(
student = student,
semester = semester,
- start = now().nextOrSameSchoolDay,
- end = now().nextOrSameSchoolDay,
+ start = startDate,
+ end = endDate,
forceRefresh = true,
notify = notify,
)
.waitForResult()
- timetableRepository.getTimetableFromDatabase(semester, now(), now().plusDays(7))
- .first()
+ timetableRepository.getTimetableFromDatabase(
+ semester = semester,
+ start = startDate,
+ end = endDate,
+ )
.filterNot { it.isNotified }
.let {
if (it.isNotEmpty()) changeTimetableNotification.notify(it, student)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt
index f622209a7..29996db7c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt
@@ -11,6 +11,7 @@ import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.auth.AuthDialog
+import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.FragmentLifecycleLogger
import io.github.wulkanowy.utils.getThemeAttrColor
@@ -68,11 +69,24 @@ abstract class BaseActivity, VB : ViewBinding> :
} else Toast.makeText(this, text, Toast.LENGTH_LONG).show()
}
- override fun showExpiredDialog() {
+ override fun showExpiredCredentialsDialog() {
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.main_expired_credentials_title)
+ .setMessage(R.string.main_expired_credentials_description)
+ .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmExpiredCredentialsSelected() }
+ .setNegativeButton(android.R.string.cancel) { _, _ -> }
+ .show()
+ }
+
+ override fun onCaptchaVerificationRequired(url: String?) {
+ CaptchaDialog.newInstance(url).show(supportFragmentManager, "captcha_dialog")
+ }
+
+ override fun showDecryptionFailedDialog() {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.main_session_expired)
.setMessage(R.string.main_session_relogin)
- .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() }
+ .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmDecryptionFailedSelected() }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt
index 84540b1ca..e63887b8f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt
@@ -8,7 +8,6 @@ import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.viewbinding.ViewBinding
import com.google.android.material.elevation.SurfaceColors
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.lifecycleAwareVariable
import javax.inject.Inject
@@ -28,8 +27,16 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
}
- override fun showExpiredDialog() {
- (activity as? BaseActivity<*, *>)?.showExpiredDialog()
+ override fun showExpiredCredentialsDialog() {
+ (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
+ }
+
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
+ override fun showDecryptionFailedDialog() {
+ (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
override fun openClearLoginView() {
@@ -41,7 +48,7 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun showErrorDetailsDialog(error: Throwable) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt
index b25346a7e..ba346131c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt
@@ -7,7 +7,6 @@ import androidx.viewbinding.ViewBinding
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
import io.github.wulkanowy.R
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.utils.lifecycleAwareVariable
abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId),
@@ -39,12 +38,20 @@ abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragme
}
}
- override fun showExpiredDialog() {
- (activity as? BaseActivity<*, *>)?.showExpiredDialog()
+ override fun showExpiredCredentialsDialog() {
+ (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
+ }
+
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
+ override fun showDecryptionFailedDialog() {
+ (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun openClearLoginView() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
index 2d913103b..d4cb20cac 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
@@ -28,20 +28,38 @@ open class BasePresenter(
this.view = view
errorHandler.apply {
showErrorMessage = view::showError
- onSessionExpired = view::showExpiredDialog
+ onExpiredCredentials = view::showExpiredCredentialsDialog
+ onCaptchaVerificationRequired = view::onCaptchaVerificationRequired
+ onDecryptionFailed = view::showDecryptionFailedDialog
onNoCurrentStudent = view::openClearLoginView
onPasswordChangeRequired = view::showChangePasswordSnackbar
onAuthorizationRequired = view::showAuthDialog
}
}
- fun onExpiredLoginSelected() {
- Timber.i("Attempt to switch the student after the session expires")
+ fun onConfirmDecryptionFailedSelected() {
+ Timber.i("Attempt to clear all data")
+
+ presenterScope.launch {
+ runCatching { studentRepository.clearAll() }
+ .onFailure {
+ Timber.i("Clear data result: An exception occurred")
+ errorHandler.dispatch(it)
+ }
+ .onSuccess {
+ Timber.i("Clear data result: Open login view")
+ view?.openClearLoginView()
+ }
+ }
+ }
+
+ fun onConfirmExpiredCredentialsSelected() {
+ Timber.i("Attempt to delete students associated with the account and switch to new student")
presenterScope.launch {
runCatching {
val student = studentRepository.getCurrentStudent(false)
- studentRepository.logoutStudent(student)
+ studentRepository.deleteStudentsAssociatedWithAccount(student)
val students = studentRepository.getSavedStudents(false)
if (students.isNotEmpty()) {
@@ -50,11 +68,11 @@ open class BasePresenter(
}
}
.onFailure {
- Timber.i("Switch student result: An exception occurred")
+ Timber.i("Delete students result: An exception occurred")
errorHandler.dispatch(it)
}
.onSuccess {
- Timber.i("Switch student result: Open login view")
+ Timber.i("Delete students result: Open login view")
view?.openClearLoginView()
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt
index b31737e2b..88d5754f8 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt
@@ -6,7 +6,11 @@ interface BaseView {
fun showMessage(text: String)
- fun showExpiredDialog()
+ fun showExpiredCredentialsDialog()
+
+ fun onCaptchaVerificationRequired(url: String?)
+
+ fun showDecryptionFailedDialog()
fun showAuthDialog()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
index 0a41a47b3..7109f1ffd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
@@ -4,6 +4,7 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException
+import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
import io.github.wulkanowy.utils.getErrorString
@@ -15,7 +16,9 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
- var onSessionExpired: () -> Unit = {}
+ var onExpiredCredentials: () -> Unit = {}
+
+ var onDecryptionFailed: () -> Unit = {}
var onNoCurrentStudent: () -> Unit = {}
@@ -23,24 +26,33 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
var onAuthorizationRequired: () -> Unit = {}
+ var onCaptchaVerificationRequired: (url: String?) -> Unit = {}
+
fun dispatch(error: Throwable) {
Timber.e(error, "An exception occurred while the Wulkanowy was running")
proceed(error)
}
protected open fun proceed(error: Throwable) {
- showErrorMessage(context.resources.getErrorString(error), error)
+ showDefaultMessage(error)
when (error) {
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
- is ScramblerException, is BadCredentialsException -> onSessionExpired()
+ is ScramblerException -> onDecryptionFailed()
+ is BadCredentialsException -> onExpiredCredentials()
is NoCurrentStudentException -> onNoCurrentStudent()
is AuthorizationRequiredException -> onAuthorizationRequired()
+ is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl)
}
}
+ fun showDefaultMessage(error: Throwable) {
+ showErrorMessage(context.resources.getErrorString(error), error)
+ }
+
open fun clear() {
showErrorMessage = { _, _ -> }
- onSessionExpired = {}
+ onExpiredCredentials = {}
+ onDecryptionFailed = {}
onNoCurrentStudent = {}
onPasswordChangeRequired = {}
onAuthorizationRequired = {}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt
index 39f376f65..4e9baac3a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt
@@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.attendance
+import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -10,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.enums.SentExcuseStatus
import io.github.wulkanowy.databinding.ItemAttendanceBinding
import io.github.wulkanowy.utils.descriptionRes
+import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.isExcusableOrNotExcused
import javax.inject.Inject
@@ -39,7 +41,33 @@ class AttendanceAdapter @Inject constructor() :
root.context.getString(R.string.all_no_data)
}
attendanceItemDescription.setText(item.descriptionRes)
- attendanceItemAlert.isVisible = item.let { it.absence && !it.excused }
+
+ attendanceItemDescription.setTextColor(
+ root.context.getThemeAttrColor(
+ when {
+ item.absence && !item.excused -> R.attr.colorAttendanceAbsence
+ item.lateness && !item.excused -> R.attr.colorAttendanceLateness
+ else -> android.R.attr.textColorSecondary
+ }
+ )
+ )
+
+ if (item.exemption || item.excused) {
+ attendanceItemDescription.setTypeface(null, Typeface.BOLD)
+ } else {
+ attendanceItemDescription.setTypeface(null, Typeface.NORMAL)
+ }
+
+ attendanceItemAlert.isVisible =
+ item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) }
+
+ attendanceItemAlert.setColorFilter(root.context.getThemeAttrColor(
+ when{
+ item.absence && !item.excused -> R.attr.colorAttendanceAbsence
+ item.lateness && !item.excused -> R.attr.colorAttendanceLateness
+ else -> android.R.attr.colorPrimary
+ }
+ ))
attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseInfo.visibility = View.GONE
attendanceItemExcuseCheckbox.visibility = View.GONE
@@ -54,10 +82,12 @@ class AttendanceAdapter @Inject constructor() :
attendanceItemExcuseInfo.visibility = View.VISIBLE
attendanceItemAlert.visibility = View.INVISIBLE
}
+
SentExcuseStatus.DENIED -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
attendanceItemExcuseInfo.visibility = View.VISIBLE
}
+
else -> {
if (item.isExcusableOrNotExcused && excuseActionMode) {
attendanceItemNumber.visibility = View.GONE
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 c0026bee5..635033132 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
@@ -6,10 +6,12 @@ import android.view.View
import androidx.core.os.bundleOf
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.descriptionRes
+import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString
@@ -44,6 +46,16 @@ class AttendanceDialog : BaseDialogFragment() {
with(binding) {
attendanceDialogSubjectValue.text = attendance.subject
attendanceDialogDescriptionValue.setText(attendance.descriptionRes)
+ attendanceDialogDescriptionValue.setTextColor(
+ root.context.getThemeAttrColor(
+ when {
+ attendance.absence && !attendance.excused -> R.attr.colorAttendanceAbsence
+ attendance.lateness && !attendance.excused -> R.attr.colorAttendanceLateness
+ else -> android.R.attr.textColorSecondary
+ }
+ )
+ )
+
attendanceDialogDateValue.text = attendance.date.toFormattedString()
attendanceDialogNumberValue.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() }
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 6e842b4d7..9b45352bd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
@@ -2,8 +2,14 @@ package io.github.wulkanowy.ui.modules.attendance
import android.content.DialogInterface.BUTTON_POSITIVE
import android.os.Bundle
-import android.view.*
-import android.view.View.*
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.View.GONE
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
@@ -63,6 +69,8 @@ class AttendanceFragment : BaseFragment(R.layout.frag
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
val inflater = mode.menuInflater
inflater.inflate(R.menu.context_menu_attendance, menu)
+ menu.findItem(R.id.excuseMenuDaySubmit).setVisible(presenter.isWholeDayExcusable)
+
return true
}
@@ -78,6 +86,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
return when (menu.itemId) {
+ R.id.excuseMenuDaySubmit -> presenter.onExcuseDayButtonClick()
R.id.excuseMenuSubmit -> presenter.onExcuseSubmitButtonClick()
else -> false
}
@@ -257,11 +266,18 @@ class AttendanceFragment : BaseFragment(R.layout.frag
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
}
- override fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String) {
- val reasonFullText = getString(
- R.string.attendance_excuse_formula,
+ override fun startSendMessageIntent(date: LocalDate, lessons: String, reason: String) {
+ val reasonFullText = if (lessons.isEmpty()) {
+ getString(
+ R.string.attendance_excuse_day_formula,
+ date,
+ if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
+ reason.ifBlank { "" }
+ )
+ } else getString(
+ R.string.attendance_excuse_lessons_formula,
date,
- numbers,
+ lessons,
if (reason.isNotBlank()) " ${getString(R.string.attendance_excuse_reason)} " else "",
reason.ifBlank { "" }
)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
index f66479daf..6b001e1d7 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
@@ -4,18 +4,14 @@ import android.annotation.SuppressLint
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
-import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
-import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.*
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import java.time.DayOfWeek
@@ -42,6 +38,7 @@ class AttendancePresenter @Inject constructor(
private lateinit var lastError: Throwable
private val attendanceToExcuseList = mutableListOf()
+ var isWholeDayExcusable = false
private var isVulcanExcusedFunctionEnabled = false
@@ -135,6 +132,12 @@ class AttendancePresenter @Inject constructor(
view?.startActionMode()
}
+ fun onExcuseDayButtonClick(): Boolean {
+ view?.showExcuseDialog()
+
+ return true
+ }
+
fun onExcuseCheckboxSelect(attendanceItem: Attendance, checked: Boolean) {
if (checked) attendanceToExcuseList.add(attendanceItem)
else attendanceToExcuseList.remove(attendanceItem)
@@ -156,7 +159,7 @@ class AttendancePresenter @Inject constructor(
fun onExcuseDialogSubmit(reason: String) {
view?.finishActionMode()
- if (attendanceToExcuseList.isEmpty()) return
+ if (attendanceToExcuseList.isEmpty() && !isWholeDayExcusable) return
if (isVulcanExcusedFunctionEnabled) {
excuseAbsence(
@@ -167,8 +170,8 @@ class AttendancePresenter @Inject constructor(
val attendanceToExcuseNumbers = attendanceToExcuseList.map { it.number }
view?.startSendMessageIntent(
- date = attendanceToExcuseList[0].date,
- numbers = attendanceToExcuseNumbers.joinToString(", "),
+ date = currentDate ?: attendanceToExcuseList[0].date,
+ lessons = attendanceToExcuseNumbers.joinToString(", "),
reason = reason
)
}
@@ -210,7 +213,7 @@ class AttendancePresenter @Inject constructor(
val semester = semesterRepository.getCurrentSemester(student)
- checkInitialAndCurrentDate(student, semester)
+ checkInitialAndCurrentDate(semester)
attendanceRepository.getAttendance(
student = student,
semester = semester,
@@ -221,7 +224,9 @@ class AttendancePresenter @Inject constructor(
}
.logResourceStatus("load attendance")
.onResourceLoading {
- view?.showExcuseButton(false)
+ view?.apply {
+ showExcuseButton(false)
+ }
}
.mapResourceData {
if (prefRepository.isShowPresent) {
@@ -244,15 +249,16 @@ class AttendancePresenter @Inject constructor(
}
}
.onResourceIntermediate { view?.showRefresh(true) }
- .onResourceSuccess {
- isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable }
- val anyExcusables = it.any { it.isExcusableOrNotExcused }
+ .onResourceSuccess { items ->
+ isVulcanExcusedFunctionEnabled = items.any { item -> item.excusable }
+ isWholeDayExcusable = items.all { it.isAbsenceExcusable }
+ val anyExcusables = items.any { it.isExcusableOrNotExcused }
view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
analytics.logEvent(
"load_data",
"type" to "attendance",
- "items" to it.size
+ "items" to items.size
)
}
.onResourceNotLoading {
@@ -266,15 +272,13 @@ class AttendancePresenter @Inject constructor(
.launch()
}
- private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
+ private suspend fun checkInitialAndCurrentDate(semester: Semester) {
if (initialDate == null) {
- val lessons = attendanceRepository.getAttendance(
- student = student,
+ val lessons = attendanceRepository.getAttendanceFromDatabase(
semester = semester,
start = now().monday,
end = now().sunday,
- forceRefresh = false,
- ).toFirstResult().dataOrNull.orEmpty()
+ ).firstOrNull().orEmpty()
isWeekendHasLessons = isWeekendHasLessons(lessons)
initialDate = getInitialDate(semester)
}
@@ -307,7 +311,19 @@ class AttendancePresenter @Inject constructor(
resourceFlow {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
- attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason)
+ if (toExcuseList.isEmpty()) {
+ attendanceRepository.excuseForAbsence(
+ student = student,
+ semester = semester,
+ days = listOfNotNull(currentDate),
+ reason = reason
+ )
+ } else attendanceRepository.excuseForAbsence(
+ student = student,
+ semester = semester,
+ absenceList = toExcuseList,
+ reason = reason
+ )
}.onEach {
when (it) {
is Resource.Loading -> view?.run {
@@ -316,6 +332,7 @@ class AttendancePresenter @Inject constructor(
showContent(false)
showExcuseButton(false)
}
+
is Resource.Success -> {
Timber.i("Excusing for absence result: Success")
analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size)
@@ -328,6 +345,7 @@ class AttendancePresenter @Inject constructor(
}
loadData(forceRefresh = true)
}
+
is Resource.Error -> {
Timber.i("Excusing for absence result: An exception occurred")
errorHandler.dispatch(it.error)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
index 2629c217e..8cfaf1591 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
@@ -56,7 +56,7 @@ interface AttendanceView : BaseView {
fun openSummaryView()
- fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String)
+ fun startSendMessageIntent(date: LocalDate, lessons: String, reason: String)
fun startActionMode()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt
index 8f579712b..3c061f498 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt
@@ -62,7 +62,11 @@ class AuthPresenter @Inject constructor(
}
isSuccess
}
- .onFailure { errorHandler.dispatch(it) }
+ .onFailure {
+ errorHandler.dispatch(it)
+ view?.showProgress(false)
+ view?.showContent(true)
+ }
.onSuccess {
if (it) {
view?.showSuccess(true)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
new file mode 100644
index 000000000..ed8293a9f
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt
@@ -0,0 +1,86 @@
+package io.github.wulkanowy.ui.modules.captcha
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.core.os.bundleOf
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.databinding.DialogCaptchaBinding
+import io.github.wulkanowy.sdk.Sdk
+import io.github.wulkanowy.ui.base.BaseDialogFragment
+import timber.log.Timber
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class CaptchaDialog : BaseDialogFragment() {
+
+ @Inject
+ lateinit var sdk: Sdk
+
+ private var webView: WebView? = null
+
+ companion object {
+ const val CAPTCHA_SUCCESS = "captcha_success"
+ private const val CAPTCHA_URL = "captcha_url"
+ private const val CAPTCHA_CHECK_JS = "document.getElementById('challenge-running') == null"
+
+ fun newInstance(url: String?): CaptchaDialog {
+ return CaptchaDialog().apply {
+ arguments = bundleOf(CAPTCHA_URL to url)
+ }
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View = DialogCaptchaBinding.inflate(inflater).apply { binding = this }.root
+
+ @SuppressLint("SetJavaScriptEnabled")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ isCancelable = false
+ binding.captchaRefresh.setOnClickListener {
+ binding.captchaWebview.loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
+ }
+ binding.captchaClose.setOnClickListener { dismiss() }
+
+ with(binding.captchaWebview) {
+ webView = this
+ with(settings) {
+ javaScriptEnabled = true
+ userAgentString = sdk.userAgent
+ }
+
+ webViewClient = object : WebViewClient() {
+ override fun onPageFinished(view: WebView?, url: String?) {
+ view?.evaluateJavascript(CAPTCHA_CHECK_JS) {
+ if (it == "true") {
+ onChallengeAccepted()
+ }
+ }
+ }
+ }
+
+ loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
+ }
+ }
+
+ private fun onChallengeAccepted() {
+ runCatching { parentFragmentManager.setFragmentResult(CAPTCHA_SUCCESS, bundleOf()) }
+ .onFailure { Timber.e(it) }
+ showMessage(getString(R.string.captcha_verified_message))
+ dismissAllowingStateLoss()
+ }
+
+ override fun onDestroy() {
+ webView?.destroy()
+ super.onDestroy()
+ }
+}
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 ce17c7632..b7a0796c5 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
@@ -18,8 +18,10 @@ import io.github.wulkanowy.databinding.FragmentDashboardBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
+import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog.Companion.CAPTCHA_SUCCESS
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
+import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
@@ -30,7 +32,12 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.capitalise
+import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.getErrorString
+import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.openInternetBrowser
+import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalDate
import javax.inject.Inject
@@ -57,6 +64,9 @@ class DashboardFragment : BaseFragment(R.layout.fragme
return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt()
}
+ override val isViewEmpty
+ get() = dashboardAdapter.itemCount == 0
+
companion object {
fun newInstance() = DashboardFragment()
@@ -72,6 +82,13 @@ class DashboardFragment : BaseFragment(R.layout.fragme
super.onViewCreated(view, savedInstanceState)
binding = FragmentDashboardBinding.bind(view)
presenter.onAttachView(this)
+ initializeCaptchaResultObserver()
+ }
+
+ private fun initializeCaptchaResultObserver() {
+ childFragmentManager.setFragmentResultListener(CAPTCHA_SUCCESS, this) { _, _ ->
+ presenter.onRetryAfterCaptcha()
+ }
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@@ -182,8 +199,17 @@ class DashboardFragment : BaseFragment(R.layout.fragme
binding.dashboardRecycler.isVisible = show
}
- override fun showErrorView(show: Boolean) {
+ override fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages?) {
binding.dashboardErrorContainer.isVisible = show
+ binding.dashboardErrorAdminMessage.root.isVisible = adminMessageItem != null
+
+ if (adminMessageItem != null) {
+ AdminMessageViewHolder(
+ binding = binding.dashboardErrorAdminMessage,
+ onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
+ onAdminMessageClickListener = presenter::onAdminMessageSelected,
+ ).bind(adminMessageItem.adminMessage)
+ }
}
override fun setErrorDetails(error: Throwable) {
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 ae451ae15..3fec62562 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -1,19 +1,48 @@
package io.github.wulkanowy.ui.modules.dashboard
-import io.github.wulkanowy.data.*
+import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageType
-import io.github.wulkanowy.data.repositories.*
+import io.github.wulkanowy.data.errorOrNull
+import io.github.wulkanowy.data.flatResourceFlow
+import io.github.wulkanowy.data.mapResourceData
+import io.github.wulkanowy.data.onResourceError
+import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
+import io.github.wulkanowy.data.repositories.ConferenceRepository
+import io.github.wulkanowy.data.repositories.ExamRepository
+import io.github.wulkanowy.data.repositories.GradeRepository
+import io.github.wulkanowy.data.repositories.HomeworkRepository
+import io.github.wulkanowy.data.repositories.LuckyNumberRepository
+import io.github.wulkanowy.data.repositories.MessageRepository
+import io.github.wulkanowy.data.repositories.PreferencesRepository
+import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
+import io.github.wulkanowy.data.repositories.SemesterRepository
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
+import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.nextOrSameSchoolDay
-import kotlinx.coroutines.flow.*
+import io.github.wulkanowy.utils.sunday
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emitAll
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.Instant
@@ -29,6 +58,7 @@ class DashboardPresenter @Inject constructor(
private val messageRepository: MessageRepository,
private val attendanceSummaryRepository: AttendanceSummaryRepository,
private val timetableRepository: TimetableRepository,
+ private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase,
private val homeworkRepository: HomeworkRepository,
private val examRepository: ExamRepository,
private val conferenceRepository: ConferenceRepository,
@@ -48,6 +78,11 @@ class DashboardPresenter @Inject constructor(
private val firstLoadedItemList = mutableListOf()
+ private val selectedDashboardTiles
+ get() = preferencesRepository.selectedDashboardTiles
+ .filterNot { it == DashboardItem.Tile.ADS && !adsHelper.canShowAd }
+ .toSet()
+
private lateinit var lastError: Throwable
override fun onAttachView(view: DashboardView) {
@@ -59,10 +94,19 @@ class DashboardPresenter @Inject constructor(
showContent(false)
}
+ val selectedDashboardTilesFlow = preferencesRepository.selectedDashboardTilesFlow
+ .map { selectedDashboardTiles }
+ val isAdsEnabledFlow = preferencesRepository.isAdsEnabledFlow
+ .filter { (adsHelper.canShowAd && it) || !it }
+ .map { selectedDashboardTiles }
+ val isMobileAdsSdkInitializedFlow = adsHelper.isMobileAdsSdkInitialized
+ .filter { it }
+ .map { selectedDashboardTiles }
+
merge(
- preferencesRepository.selectedDashboardTilesFlow,
- preferencesRepository.isAdsEnabledFlow
- .map { preferencesRepository.selectedDashboardTiles }
+ selectedDashboardTilesFlow,
+ isAdsEnabledFlow,
+ isMobileAdsSdkInitializedFlow
)
.onEach { loadData(tilesToLoad = it) }
.launch("dashboard_pref")
@@ -71,7 +115,7 @@ class DashboardPresenter @Inject constructor(
fun onAdminMessageDismissed(adminMessage: AdminMessage) {
preferencesRepository.dismissedAdminMessageIds += adminMessage.id
- loadData(preferencesRepository.selectedDashboardTiles)
+ loadData(selectedDashboardTiles)
}
fun onDragAndDropEnd(list: List) {
@@ -187,7 +231,7 @@ class DashboardPresenter @Inject constructor(
fun onSwipeRefresh() {
Timber.i("Force refreshing the dashboard")
- loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
+ loadData(selectedDashboardTiles, forceRefresh = true)
}
fun onRetry() {
@@ -195,7 +239,15 @@ class DashboardPresenter @Inject constructor(
showErrorView(false)
showProgress(true)
}
- loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
+ loadData(selectedDashboardTiles, forceRefresh = true)
+ }
+
+ fun onRetryAfterCaptcha() {
+ view?.run {
+ showErrorView(false)
+ showProgress(true)
+ }
+ loadData(selectedDashboardTiles, forceRefresh = true)
}
fun onViewReselected() {
@@ -216,7 +268,7 @@ class DashboardPresenter @Inject constructor(
}
fun onDashboardTileSettingsSelected(): Boolean {
- view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
+ view?.showDashboardTileSettings(selectedDashboardTiles.toList())
return true
}
@@ -232,7 +284,7 @@ class DashboardPresenter @Inject constructor(
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow {
- val selectedTiles = preferencesRepository.selectedDashboardTiles
+ val selectedTiles = selectedDashboardTiles
val flowSuccess = flowOf(Resource.Success(null))
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
@@ -252,6 +304,7 @@ class DashboardPresenter @Inject constructor(
forceRefresh = forceRefresh
)
}
+ .mapResourceData { it.map { messageWithAuthor -> messageWithAuthor.message } }
.onResourceError { errorHandler.dispatch(it) }
.takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
@@ -275,7 +328,7 @@ class DashboardPresenter @Inject constructor(
) { luckyNumberResource, messageResource, attendanceResource ->
val resList = listOf(luckyNumberResource, messageResource, attendanceResource)
- DashboardItem.HorizontalGroup(
+ resList to DashboardItem.HorizontalGroup(
isLoading = resList.any { it is Resource.Loading },
error = resList.map { it.errorOrNull }.let { errors ->
if (errors.all { it != null }) {
@@ -300,9 +353,9 @@ class DashboardPresenter @Inject constructor(
)
})
}
- .filterNot { it.isLoading && forceRefresh }
+ .filterNot { (_, it) -> it.isLoading && forceRefresh }
.distinctUntilChanged()
- .onEach {
+ .onEach { (_, it) ->
updateData(it, forceRefresh)
if (it.isLoading) {
@@ -320,7 +373,7 @@ class DashboardPresenter @Inject constructor(
)
errorHandler.dispatch(it)
}
- .launch("horizontal_group ${if (forceRefresh) "-forceRefresh" else ""}")
+ .launchWithUniqueRefreshJob("horizontal_group", forceRefresh)
}
private fun loadGrades(student: Student, forceRefresh: Boolean) {
@@ -354,7 +407,7 @@ class DashboardPresenter @Inject constructor(
subjectWithGrades = it.dataOrNull,
gradeTheme = preferencesRepository.gradeColorTheme,
isLoading = true
- ), forceRefresh
+ ), false
)
if (!it.dataOrNull.isNullOrEmpty()) {
@@ -386,14 +439,17 @@ class DashboardPresenter @Inject constructor(
private fun loadLessons(student: Student, forceRefresh: Boolean) {
flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
- val date = LocalDate.now()
+ val date = when (isStudentHasLessonsOnWeekendUseCase(semester)) {
+ true -> LocalDate.now()
+ else -> LocalDate.now().nextOrSameSchoolDay
+ }
timetableRepository.getTimetable(
student = student,
semester = semester,
start = date,
- end = date.plusDays(1),
- forceRefresh = forceRefresh
+ end = date.sunday,
+ forceRefresh = forceRefresh,
)
}
.onEach {
@@ -403,7 +459,7 @@ class DashboardPresenter @Inject constructor(
if (forceRefresh) return@onEach
updateData(
DashboardItem.Lessons(it.dataOrNull, isLoading = true),
- forceRefresh
+ false
)
if (!it.dataOrNull?.lessons.isNullOrEmpty()) {
@@ -460,7 +516,7 @@ class DashboardPresenter @Inject constructor(
val data = it.dataOrNull.orEmpty()
updateData(
DashboardItem.Homework(data, isLoading = true),
- forceRefresh
+ false
)
if (data.isNotEmpty()) {
@@ -494,7 +550,7 @@ class DashboardPresenter @Inject constructor(
if (forceRefresh) return@onEach
updateData(
DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true),
- forceRefresh
+ false
)
if (!it.dataOrNull.isNullOrEmpty()) {
@@ -537,7 +593,7 @@ class DashboardPresenter @Inject constructor(
if (forceRefresh) return@onEach
updateData(
DashboardItem.Exams(it.dataOrNull.orEmpty(), isLoading = true),
- forceRefresh
+ false
)
if (!it.dataOrNull.isNullOrEmpty()) {
@@ -578,7 +634,7 @@ class DashboardPresenter @Inject constructor(
if (forceRefresh) return@onEach
updateData(
DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true),
- forceRefresh
+ false
)
if (!it.dataOrNull.isNullOrEmpty()) {
@@ -613,7 +669,7 @@ class DashboardPresenter @Inject constructor(
is Resource.Loading -> {
Timber.i("Loading dashboard admin message data started")
if (forceRefresh) return@onEach
- updateData(DashboardItem.AdminMessages(), forceRefresh)
+ updateData(DashboardItem.AdminMessages(), false)
}
is Resource.Success -> {
@@ -643,7 +699,7 @@ class DashboardPresenter @Inject constructor(
private fun loadAds(forceRefresh: Boolean) {
presenterScope.launch {
if (!forceRefresh) {
- updateData(DashboardItem.Ads(), forceRefresh)
+ updateData(DashboardItem.Ads(), false)
}
val dashboardAdItem =
@@ -764,6 +820,8 @@ class DashboardPresenter @Inject constructor(
val filteredItems = itemsLoadedList.filterNot {
it.type == DashboardItem.Type.ACCOUNT || it.type == DashboardItem.Type.ADMIN_MESSAGE
}
+ val dataLoadedAdminMessageItem =
+ itemsLoadedList.find { it.type == DashboardItem.Type.ADMIN_MESSAGE && it.isDataLoaded } as DashboardItem.AdminMessages?
val isAccountItemError =
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
val isGeneralError =
@@ -785,7 +843,7 @@ class DashboardPresenter @Inject constructor(
showRefresh(false)
if ((forceRefresh && wasGeneralError) || !forceRefresh) {
showContent(false)
- showErrorView(true)
+ showErrorView(true, dataLoadedAdminMessageItem)
setErrorDetails(lastError)
}
}
@@ -813,6 +871,28 @@ class DashboardPresenter @Inject constructor(
onEach {
if (it is Resource.Success) {
cancelJobs(jobName)
+ } else if (it is Resource.Error) {
+ cancelJobs(jobName)
+ }
+ }.launch(jobName)
+ } else {
+ launch(jobName)
+ }
+ }
+
+ @JvmName("launchWithUniqueRefreshJobHorizontalGroup")
+ private fun Flow>, *>>.launchWithUniqueRefreshJob(
+ name: String,
+ forceRefresh: Boolean
+ ) {
+ val jobName = if (forceRefresh) "$name-forceRefresh" else name
+
+ if (forceRefresh) {
+ onEach { (resources, _) ->
+ if (resources.all { it is Resource.Success<*> }) {
+ cancelJobs(jobName)
+ } else if (resources.any { it is Resource.Error<*> }) {
+ cancelJobs(jobName)
}
}.launch(jobName)
} else {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
index 767885434..56a0a773a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
@@ -6,6 +6,8 @@ interface DashboardView : BaseView {
val tileWidth: Int
+ val isViewEmpty: Boolean
+
fun initView()
fun updateData(data: List)
@@ -18,7 +20,7 @@ interface DashboardView : BaseView {
fun showRefresh(show: Boolean)
- fun showErrorView(show: Boolean)
+ fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages? = null)
fun setErrorDetails(error: Throwable)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt
index 81099801a..1e0f0bdbf 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt
@@ -7,7 +7,6 @@ import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
-import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.utils.getThemeAttrColor
class AdminMessageViewHolder(
@@ -25,9 +24,11 @@ class AdminMessageViewHolder(
context.getThemeAttrColor(R.attr.colorMessageHigh) to
context.getThemeAttrColor(R.attr.colorOnMessageHigh)
}
+
"MEDIUM" -> {
context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
}
+
else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
}
@@ -37,11 +38,16 @@ class AdminMessageViewHolder(
dashboardAdminMessageItemDescription.text = item.content
dashboardAdminMessageItemDescription.setTextColor(textColor)
dashboardAdminMessageItemIcon.setColorFilter(textColor)
- dashboardAdminMessageItemDismiss.isVisible = item.isDismissible
+ dashboardAdminMessageItemDismiss.isVisible = item.isOkVisible
+ dashboardAdminMessageItemClose.isVisible = item.isXVisible
dashboardAdminMessageItemDismiss.setTextColor(textColor)
+ dashboardAdminMessageItemClose.imageTintList = ColorStateList.valueOf(textColor)
dashboardAdminMessageItemDismiss.setOnClickListener {
onAdminMessageDismissClickListener(item)
}
+ dashboardAdminMessageItemClose.setOnClickListener {
+ onAdminMessageDismissClickListener(item)
+ }
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
item.destinationUrl?.let { url ->
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt
index 000916b17..9db01a307 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt
@@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.debug
import android.os.Bundle
import android.view.View
+import android.webkit.CookieManager
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
@@ -58,6 +59,10 @@ class DebugFragment : BaseFragment(R.layout.fragment_debug
(activity as? MainActivity)?.pushView(NotificationDebugFragment.newInstance())
}
+ override fun clearWebkitCookies() {
+ CookieManager.getInstance().removeAllCookies(null)
+ }
+
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt
index 67ac88861..816b59858 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt
@@ -15,6 +15,7 @@ class DebugPresenter @Inject constructor(
val items = listOf(
DebugItem(R.string.logviewer_title),
DebugItem(R.string.notification_debug_title),
+ DebugItem(R.string.debug_cookies_clear),
)
override fun onAttachView(view: DebugView) {
@@ -31,6 +32,7 @@ class DebugPresenter @Inject constructor(
when (item.title) {
R.string.logviewer_title -> view?.openLogViewer()
R.string.notification_debug_title -> view?.openNotificationsDebug()
+ R.string.debug_cookies_clear -> view?.clearWebkitCookies()
else -> Timber.d("Unknown debug item: $item")
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt
index 9396ec6ac..792d63d9e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt
@@ -11,4 +11,6 @@ interface DebugView : BaseView {
fun openLogViewer()
fun openNotificationsDebug()
+
+ fun clearWebkitCookies()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt
index d0dfcd696..cdd186b94 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt
@@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems
+import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDescriptiveItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeSummaryItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugHomeworkItems
@@ -55,6 +56,14 @@ class NotificationDebugPresenter @Inject constructor(
NotificationDebugItem(R.string.grade_summary_final_grade) { n ->
withStudent { newGradeNotification.notifyFinal(debugGradeSummaryItems.take(n), it) }
},
+ NotificationDebugItem(R.string.grade_summary_descriptive) { n ->
+ withStudent {
+ newGradeNotification.notifyDescriptive(
+ debugGradeDescriptiveItems.take(n),
+ it
+ )
+ }
+ },
NotificationDebugItem(R.string.homework_title) { n ->
withStudent { newHomeworkNotification.notify(debugHomeworkItems.take(n), it) }
},
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt
new file mode 100644
index 000000000..d5a0c089b
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt
@@ -0,0 +1,48 @@
+package io.github.wulkanowy.ui.modules.debug.notification.mock
+
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
+
+val debugGradeDescriptiveItems = listOf(
+ generateGradeDescriptive(
+ "Matematyka",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive("Fizyka", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
+ generateGradeDescriptive(
+ "Geografia",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Sieci komputerowe",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Systemy operacyjne",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Język polski",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Język angielski",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive("Religia", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
+ generateGradeDescriptive(
+ "Język niemiecki",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+ generateGradeDescriptive(
+ "Wychowanie fizyczne",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ ),
+)
+
+private fun generateGradeDescriptive(subject: String, description: String) =
+ GradeDescriptive(
+ semesterId = 0,
+ studentId = 0,
+ subject = subject,
+ description = description
+ )
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
index ec4bd8e8c..e8a5fa254 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
@@ -1,15 +1,23 @@
package io.github.wulkanowy.ui.modules.grade
-import io.github.wulkanowy.data.*
+import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.errorOrNull
+import io.github.wulkanowy.data.flatResourceFlow
+import io.github.wulkanowy.data.mapData
+import io.github.wulkanowy.data.mapResourceData
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.*
+import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR
+import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS
+import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER
import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,6 +70,7 @@ class GradeAverageProvider @Inject constructor(
forceRefresh = forceRefresh,
params = params,
)
+
BOTH_SEMESTERS -> calculateCombinedAverage(
student = student,
semesters = semesters,
@@ -69,6 +78,7 @@ class GradeAverageProvider @Inject constructor(
forceRefresh = forceRefresh,
config = params,
)
+
ALL_YEAR -> calculateCombinedAverage(
student = student,
semesters = semesters,
@@ -189,36 +199,73 @@ class GradeAverageProvider @Inject constructor(
): Flow>> {
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
.mapResourceData { res ->
- val (details, summaries) = res
+ val (details, summaries, descriptives) = res
val isAnyAverage = summaries.any { it.average != .0 }
val allGrades = details.groupBy { it.subject }
+ val descriptiveGradesBySubject = descriptives.associateBy { it.subject }
- val items = summaries.emulateEmptySummaries(
- student = student,
- semester = semester,
- grades = allGrades.toList(),
- calcAverage = isAnyAverage,
- params = params,
- ).map { summary ->
- val grades = allGrades[summary.subject].orEmpty()
- GradeSubject(
- subject = summary.subject,
- average = if (!isAnyAverage || params.forceAverageCalc) {
- grades.updateModifiers(student, params)
- .calcAverage(params.isOptionalArithmeticAverage)
- } else summary.average,
- points = summary.pointsSum,
- summary = summary,
- grades = grades,
- isVulcanAverage = isAnyAverage
+ val items = summaries
+ .createEmptySummariesByGradesIfNeeded(
+ student = student,
+ semester = semester,
+ grades = allGrades.toList(),
+ calcAverage = isAnyAverage,
+ params = params,
)
- }
+ .createEmptySummariesByDescriptiveGradesIfNeeded(
+ student = student,
+ semester = semester,
+ descriptives = descriptives,
+ )
+ .map { summary ->
+ val grades = allGrades[summary.subject].orEmpty()
+ val descriptiveGrade = descriptiveGradesBySubject[summary.subject]
+
+ GradeSubject(
+ subject = summary.subject,
+ average = if (!isAnyAverage || params.forceAverageCalc) {
+ grades.updateModifiers(student, params)
+ .calcAverage(params.isOptionalArithmeticAverage)
+ } else summary.average,
+ points = summary.pointsSum,
+ summary = summary,
+ grades = grades,
+ descriptive = descriptiveGrade,
+ isVulcanAverage = isAnyAverage
+ )
+ }
items
}
}
- private fun List.emulateEmptySummaries(
+ private fun List.createEmptySummariesByDescriptiveGradesIfNeeded(
+ student: Student,
+ semester: Semester,
+ descriptives: List
+ ): List {
+ val summarySubjects = this.map { it.subject }
+ val gradeSummaryToAdd = descriptives.mapNotNull { gradeDescriptive ->
+ if (gradeDescriptive.subject in summarySubjects) return@mapNotNull null
+
+ GradeSummary(
+ studentId = student.studentId,
+ semesterId = semester.semesterId,
+ position = 0,
+ subject = gradeDescriptive.subject,
+ predictedGrade = "",
+ finalGrade = "",
+ proposedPoints = "",
+ finalPoints = "",
+ pointsSum = "",
+ average = .0
+ )
+ }
+
+ return this + gradeSummaryToAdd
+ }
+
+ private fun List.createEmptySummariesByGradesIfNeeded(
student: Student,
semester: Semester,
grades: List>>,
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt
index 57be55ee3..a465551f9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt
@@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
data class GradeSubject(
@@ -8,6 +9,7 @@ data class GradeSubject(
val average: Double,
val points: String,
val summary: GradeSummary,
+ val descriptive: GradeDescriptive?,
val grades: List,
val isVulcanAverage: Boolean
)
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 4261c507d..d9621f51e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
@@ -1,13 +1,22 @@
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.ALPHABETIC
+import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE
+import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
+import io.github.wulkanowy.data.flatResourceFlow
+import io.github.wulkanowy.data.logResourceStatus
+import io.github.wulkanowy.data.onResourceData
+import io.github.wulkanowy.data.onResourceError
+import io.github.wulkanowy.data.onResourceIntermediate
+import io.github.wulkanowy.data.onResourceNotLoading
+import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
@@ -207,20 +216,20 @@ class GradeDetailsPresenter @Inject constructor(
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
}
}
- .map { (subject, average, points, _, grades) ->
- val subItems = grades
+ .map { gradeSubject ->
+ val subItems = gradeSubject.grades
.sortedByDescending { it.date }
.map { GradeDetailsItem(it, ViewType.ITEM) }
val gradeDetailsItems = listOf(
GradeDetailsItem(
GradeDetailsHeader(
- subject = subject,
- average = average,
- pointsSum = points,
+ subject = gradeSubject.subject,
+ average = gradeSubject.average,
+ pointsSum = gradeSubject.points,
grades = subItems
).apply {
- newGrades = grades.filter { grade -> !grade.isRead }.size
+ newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size
}, ViewType.HEADER
)
)
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 8dcade56e..95cf97bed 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
@@ -2,16 +2,16 @@ package io.github.wulkanowy.ui.modules.grade.summary
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.RecyclerView
import io.github.wulkanowy.R
-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 io.github.wulkanowy.utils.ifNullOrBlank
import java.util.Locale
import javax.inject.Inject
@@ -24,7 +24,7 @@ class GradeSummaryAdapter @Inject constructor(
ITEM(2)
}
- var items = emptyList()
+ var items = emptyList()
var onCalculatedHelpClickListener: () -> Unit = {}
@@ -44,9 +44,11 @@ class GradeSummaryAdapter @Inject constructor(
ViewType.HEADER.id -> HeaderViewHolder(
ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false)
)
+
ViewType.ITEM.id -> ItemViewHolder(
ItemGradeSummaryBinding.inflate(inflater, parent, false)
)
+
else -> throw IllegalStateException()
}
}
@@ -60,19 +62,23 @@ class GradeSummaryAdapter @Inject constructor(
private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) {
if (items.isEmpty()) return
+ val gradeSummaries = items
+ .filter { it.gradeDescriptive == null }
+ .map { it.gradeSummary }
val context = binding.root.context
- 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(
+ val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) }
+ val calculatedItemsCount = gradeSummaries.count { value -> value.average != 0.0 }
+ val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) }
+ val finalAverage = gradeSummaries.calcFinalAverage(
preferencesRepository.gradePlusModifier,
preferencesRepository.gradeMinusModifier
)
- val calculatedAverage = items.filter { value -> value.average != 0.0 }
+ val calculatedAverage = gradeSummaries.filter { value -> value.average != 0.0 }
.map { values -> values.average }
.reversed() // fix average precision
.average()
+ .let { if (it.isNaN()) 0.0 else it }
with(binding) {
gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage)
@@ -95,16 +101,28 @@ class GradeSummaryAdapter @Inject constructor(
}
@SuppressLint("SetTextI18n")
- private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummary) {
- with(binding) {
- gradeSummaryItemTitle.text = item.subject
- gradeSummaryItemPoints.text = item.pointsSum
- gradeSummaryItemAverage.text = formatAverage(item.average, "")
- gradeSummaryItemPredicted.text = "${item.predictedGrade} ${item.proposedPoints}".trim()
- gradeSummaryItemFinal.text = "${item.finalGrade} ${item.finalPoints}".trim()
+ private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummaryItem) {
+ val (gradeSummary, gradeDescriptive) = item
- gradeSummaryItemPointsContainer.visibility =
- if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE
+ with(binding) {
+ gradeSummaryItemTitle.text = gradeSummary.subject
+ gradeSummaryItemPoints.text = gradeSummary.pointsSum
+ gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "")
+ gradeSummaryItemPredicted.text =
+ "${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim()
+ gradeSummaryItemFinal.text =
+ "${gradeSummary.finalGrade} ${gradeSummary.finalPoints}".trim()
+ gradeSummaryItemDescriptive.text = gradeDescriptive?.description.ifNullOrBlank {
+ root.context.getString(R.string.all_no_data)
+ }
+
+ gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null
+ gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null
+ gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null
+ gradeSummaryItemPredictedContainer.isVisible = gradeDescriptive == null
+ gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null
+ gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null
+ gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank()
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt
index abd0b13c4..35b2edd58 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt
@@ -5,12 +5,10 @@ import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
-import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
@@ -72,7 +70,7 @@ class GradeSummaryFragment :
}
}
- override fun updateData(data: List) {
+ override fun updateData(data: List) {
with(gradeSummaryAdapter) {
items = data
notifyDataSetChanged()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt
new file mode 100644
index 000000000..cf0f1d92e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt
@@ -0,0 +1,9 @@
+package io.github.wulkanowy.ui.modules.grade.summary
+
+import io.github.wulkanowy.data.db.entities.GradeDescriptive
+import io.github.wulkanowy.data.db.entities.GradeSummary
+
+data class GradeSummaryItem(
+ val gradeSummary: GradeSummary,
+ val gradeDescriptive: GradeDescriptive?
+)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
index 4d5a43d8f..d762df02b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
@@ -1,9 +1,16 @@
package io.github.wulkanowy.ui.modules.grade.summary
-import io.github.wulkanowy.data.*
-import io.github.wulkanowy.data.db.entities.GradeSummary
-import io.github.wulkanowy.data.enums.GradeSortingMode
-import io.github.wulkanowy.data.enums.GradeSortingMode.*
+import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC
+import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE
+import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
+import io.github.wulkanowy.data.flatResourceFlow
+import io.github.wulkanowy.data.logResourceStatus
+import io.github.wulkanowy.data.mapResourceData
+import io.github.wulkanowy.data.onResourceData
+import io.github.wulkanowy.data.onResourceError
+import io.github.wulkanowy.data.onResourceIntermediate
+import io.github.wulkanowy.data.onResourceNotLoading
+import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
@@ -42,7 +49,7 @@ class GradeSummaryPresenter @Inject constructor(
val student = studentRepository.getCurrentStudent()
averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh)
}
- .logResourceStatus("load grade summary", showData = true)
+ .logResourceStatus("load grade summary")
.mapResourceData { createGradeSummaryItems(it) }
.onResourceData {
view?.run {
@@ -128,7 +135,7 @@ class GradeSummaryPresenter @Inject constructor(
view?.showFinalAverageHelpDialog()
}
- private fun createGradeSummaryItems(items: List): List {
+ private fun createGradeSummaryItems(items: List): List {
return items
.filter { !checkEmpty(it) }
.let { gradeSubjects ->
@@ -136,21 +143,32 @@ class GradeSummaryPresenter @Inject constructor(
DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage ->
gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date
}
+
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
gradeDetailsWithAverage.subject.lowercase()
}
+
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
}
}
- .map { it.summary.copy(average = it.average) }
+ .map {
+ val gradeSummary = it.summary.copy(average = it.average)
+ val descriptive = it.descriptive
+ GradeSummaryItem(
+ gradeSummary = gradeSummary,
+ gradeDescriptive = descriptive,
+ )
+ }
+
}
private fun checkEmpty(gradeSummary: GradeSubject): Boolean {
return gradeSummary.run {
summary.finalGrade.isBlank()
- && summary.predictedGrade.isBlank()
- && average == .0
- && points.isBlank()
+ && summary.predictedGrade.isBlank()
+ && average == .0
+ && points.isBlank()
+ && descriptive == null
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt
index 156731c31..36bd61421 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt
@@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.grade.summary
-import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.ui.base.BaseView
interface GradeSummaryView : BaseView {
@@ -13,7 +12,7 @@ interface GradeSummaryView : BaseView {
fun initView()
- fun updateData(data: List)
+ fun updateData(data: List)
fun resetView()
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 2c11bb6d5..b066cceb9 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
@@ -7,5 +7,6 @@ data class LoginData(
val password: String,
val baseUrl: String,
val domainSuffix: String,
- val symbol: String?,
+ val defaultSymbol: String,
+ val userEnteredSymbol: String? = null,
) : Serializable
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 a17ad0035..009f26e17 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
@@ -71,7 +71,7 @@ class LoginAdvancedPresenter @Inject constructor(
fun updateUsernameLabel() {
view?.apply {
- setUsernameLabel(if ("vulcan" in formHostValue || "fakelog" in formHostValue) emailLabel else nicknameLabel)
+ setUsernameLabel(if ("vulcan" in formHostValue || "wulkanowy" in formHostValue) emailLabel else nicknameLabel)
}
}
@@ -79,7 +79,7 @@ class LoginAdvancedPresenter @Inject constructor(
view?.apply {
clearPassError()
clearUsernameError()
- if (formHostValue.contains("fakelog")) {
+ if (formHostValue.contains("wulkanowy")) {
setDefaultCredentials(
"jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999"
)
@@ -155,7 +155,7 @@ class LoginAdvancedPresenter @Inject constructor(
password = view?.formPassValue.orEmpty().trim(),
baseUrl = view?.formHostValue.orEmpty().trim(),
domainSuffix = view?.formDomainSuffix.orEmpty().trim(),
- symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
+ defaultSymbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
)
when (it.data.symbols.size) {
0 -> view?.navigateToSymbol(loginData)
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 8e9b86fa3..1c4920696 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
@@ -7,6 +7,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.setFragmentResultListener
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AdminMessage
@@ -14,6 +15,7 @@ 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.captcha.CaptchaDialog
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData
@@ -72,6 +74,13 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
super.onViewCreated(view, savedInstanceState)
binding = FragmentLoginFormBinding.bind(view)
presenter.onAttachView(this)
+ initializeCaptchaResultObserver()
+ }
+
+ private fun initializeCaptchaResultObserver() {
+ setFragmentResultListener(CaptchaDialog.CAPTCHA_SUCCESS) { _, _ ->
+ presenter.onRetryAfterCaptcha()
+ }
}
override fun initView() {
@@ -85,6 +94,7 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() }
loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() }
loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
+ loginFormDomainSuffix.doOnTextChanged { _, _, _, _ -> presenter.onDomainSuffixChanged() }
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() }
loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() }
@@ -179,6 +189,12 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
}
}
+ override fun setDomainSuffixInvalid() {
+ with(binding.loginFormDomainSuffixLayout) {
+ error = getString(R.string.login_invalid_domain_suffix)
+ }
+ }
+
override fun clearUsernameError() {
binding.loginFormUsernameLayout.error = null
binding.loginFormErrorBox.isVisible = false
@@ -197,6 +213,10 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
binding.loginFormErrorBox.isVisible = false
}
+ override fun clearDomainSuffixError() {
+ binding.loginFormDomainSuffixLayout.error = null
+ }
+
override fun showSoftKeyboard() {
activity?.showSoftInput()
}
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 ad535c382..39bc3f02d 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
@@ -14,6 +14,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
+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
@@ -90,7 +91,7 @@ class LoginFormPresenter @Inject constructor(
clearPassError()
clearUsernameError()
clearHostError()
- if (formHostValue.contains("fakelog")) {
+ if (formHostValue.contains("wulkanowy")) {
setCredentials("jan@fakelog.cf", "jan123")
} else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") {
setCredentials("", "")
@@ -101,6 +102,12 @@ class LoginFormPresenter @Inject constructor(
}
}
+ fun onDomainSuffixChanged() {
+ view?.apply {
+ clearDomainSuffixError()
+ }
+ }
+
fun updateCustomDomainSuffixVisibility() {
view?.run {
showDomainSuffixInput("customSuffix" in formHostValue)
@@ -148,14 +155,18 @@ class LoginFormPresenter @Inject constructor(
password = password,
baseUrl = host,
domainSuffix = domainSuffix,
- symbol = symbol
+ defaultSymbol = symbol
)
}
+ fun onRetryAfterCaptcha() {
+ onSignInClick()
+ }
+
fun onSignInClick() {
val loginData = getLoginData()
- if (!validateCredentials(loginData.login, loginData.password, loginData.baseUrl)) return
+ if (!validateCredentials(loginData)) return
resourceFlow {
studentRepository.getUserSubjectsFromScrapper(
@@ -163,7 +174,7 @@ class LoginFormPresenter @Inject constructor(
password = loginData.password,
scrapperBaseUrl = loginData.baseUrl,
domainSuffix = loginData.domainSuffix,
- symbol = loginData.symbol.orEmpty(),
+ symbol = loginData.defaultSymbol,
)
}
.logResourceStatus("login")
@@ -194,6 +205,9 @@ class LoginFormPresenter @Inject constructor(
}
.onResourceError {
loginErrorHandler.dispatch(it)
+ if (it is InvalidSymbolException) {
+ loginErrorHandler.showDefaultMessage(it)
+ }
lastError = it
view?.showContact(true)
analytics.logEvent(
@@ -225,24 +239,29 @@ class LoginFormPresenter @Inject constructor(
view?.onRecoverClick()
}
- private fun validateCredentials(login: String, password: String, host: String): Boolean {
+ private fun validateCredentials(loginData: LoginData): Boolean {
var isCorrect = true
- if (login.isEmpty()) {
+ if (loginData.login.isEmpty()) {
view?.setErrorUsernameRequired()
isCorrect = false
} else {
- if ("@" in login && "login" in host) {
+ if ("@" in loginData.login && "login" in loginData.baseUrl) {
view?.setErrorLoginRequired()
isCorrect = false
}
- if ("@" !in login && "email" in host) {
+ if ("@" !in loginData.login && "email" in loginData.baseUrl) {
view?.setErrorEmailRequired()
isCorrect = false
}
- if ("@" in login && "||" !in login && "login" !in host && "email" !in host) {
- val emailHost = login.substringAfter("@")
- val emailDomain = URL(host).host
+
+ val isEmailLogin = "@" in loginData.login
+ val isEmailWithLogin = "||" !in loginData.login
+ val isLoginNotRequired = "login" !in loginData.baseUrl
+ val isEmailNotRequired = "email" !in loginData.baseUrl
+ if (isEmailLogin && isEmailWithLogin && isLoginNotRequired && isEmailNotRequired) {
+ val emailHost = loginData.login.substringAfter("@")
+ val emailDomain = URL(loginData.baseUrl).host
if (!emailHost.equals(emailDomain, true)) {
view?.setErrorEmailInvalid(domain = emailDomain)
isCorrect = false
@@ -250,16 +269,21 @@ class LoginFormPresenter @Inject constructor(
}
}
- if (password.isEmpty()) {
+ if (loginData.password.isEmpty()) {
view?.setErrorPassRequired(focus = isCorrect)
isCorrect = false
}
- if (password.length < 6 && password.isNotEmpty()) {
+ if (loginData.password.length < 6 && loginData.password.isNotEmpty()) {
view?.setErrorPassInvalid(focus = isCorrect)
isCorrect = false
}
+ if (loginData.domainSuffix !in listOf("", "rc", "kurs")) {
+ view?.setDomainSuffixInvalid()
+ isCorrect = false
+ }
+
return isCorrect
}
}
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 f2b7b1003..6ea22d180 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
@@ -46,12 +46,16 @@ interface LoginFormView : BaseView {
fun setErrorEmailInvalid(domain: String)
+ fun setDomainSuffixInvalid()
+
fun clearUsernameError()
fun clearPassError()
fun clearHostError()
+ fun clearDomainSuffixError()
+
fun showSoftKeyboard()
fun hideSoftKeyboard()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt
index a424df40d..18902e014 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt
@@ -38,7 +38,7 @@ class LoginRecoverPresenter @Inject constructor(
fun onHostSelected() {
view?.run {
- if ("fakelog" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf")
+ if ("wulkanowy" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf")
clearUsernameError()
updateFields()
}
@@ -60,7 +60,7 @@ class LoginRecoverPresenter @Inject constructor(
resourceFlow {
recoverRepository.getReCaptchaSiteKey(
host,
- symbol.ifBlank { "Default" })
+ symbol.ifBlank { "default" })
}.onEach {
when (it) {
is Resource.Loading -> view?.run {
@@ -103,7 +103,7 @@ class LoginRecoverPresenter @Inject constructor(
fun onReCaptchaVerified(reCaptchaResponse: String) {
val username = view?.recoverNameValue.orEmpty()
val host = view?.recoverHostValue.orEmpty()
- val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" }
+ val symbol = view?.formHostSymbol.ifNullOrBlank { "default" }
resourceFlow {
recoverRepository.sendRecoverRequest(
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 06efd8d98..0fe36aa99 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
@@ -10,13 +10,11 @@ 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.auth.AuthDialog
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
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
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 6cbdfbb85..344414180 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
@@ -111,8 +111,8 @@ class LoginStudentSelectPresenter @Inject constructor(
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 }))
+ if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) {
+ add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol }))
}
addAll(createNotEmptySymbolItems(notEmptySymbols, students))
@@ -317,7 +317,7 @@ class LoginStudentSelectPresenter @Inject constructor(
loginData = loginData,
registerUser = registerUser,
lastErrorMessage = lastError?.message,
- enteredSymbol = loginData.symbol,
+ enteredSymbol = loginData.userEnteredSymbol,
)
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt
index fcf7f51c9..4be2dbaad 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt
@@ -105,7 +105,7 @@ class LoginSupportDialog : BaseDialogFragment() {
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
"${appInfo.versionName}-${appInfo.buildFlavor}",
- supportInfo.loginData.baseUrl + "/" + supportInfo.loginData.symbol,
+ supportInfo.loginData.let { "${it.baseUrl}/${it.defaultSymbol}/${it.userEnteredSymbol}" },
preferencesRepository.installationId,
getLastErrorFromStudentSelectScreen(),
dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() }
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 02bfde5d7..5c31f14d4 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
@@ -60,7 +60,7 @@ class LoginSymbolPresenter @Inject constructor(
}
loginData = loginData.copy(
- symbol = view?.symbolValue?.getNormalizedSymbol(),
+ userEnteredSymbol = view?.symbolValue?.getNormalizedSymbol(),
)
resourceFlow {
studentRepository.getUserSubjectsFromScrapper(
@@ -68,7 +68,7 @@ class LoginSymbolPresenter @Inject constructor(
password = loginData.password,
scrapperBaseUrl = loginData.baseUrl,
domainSuffix = loginData.domainSuffix,
- symbol = loginData.symbol.orEmpty(),
+ symbol = loginData.userEnteredSymbol.orEmpty(),
)
}.onEach { user ->
registerUser = user.dataOrNull
@@ -93,13 +93,10 @@ class LoginSymbolPresenter @Inject constructor(
else -> {
val enteredSymbolDetails = user.data.symbols
.firstOrNull()
- ?.takeIf { it.symbol == loginData.symbol }
+ ?.takeIf { it.symbol == loginData.userEnteredSymbol }
if (enteredSymbolDetails?.error is InvalidSymbolException) {
- view?.run {
- setErrorSymbolInvalid()
- showContact(true)
- }
+ showInvalidSymbolError()
} else {
Timber.i("Login with symbol result: Success")
view?.navigateToStudentSelect(loginData, requireNotNull(user.data))
@@ -128,6 +125,9 @@ class LoginSymbolPresenter @Inject constructor(
loginErrorHandler.dispatch(user.error)
lastError = user.error
view?.showContact(true)
+ if (user.error is InvalidSymbolException) {
+ showInvalidSymbolError()
+ }
}
}
}.onResourceNotLoading {
@@ -145,6 +145,13 @@ class LoginSymbolPresenter @Inject constructor(
return normalizedSymbol in definitelyInvalidSymbols
}
+ private fun showInvalidSymbolError() {
+ view?.run {
+ setErrorSymbolInvalid()
+ showContact(true)
+ }
+ }
+
fun onFaqClick() {
view?.openFaqPage()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt
index bafb2d7e5..1ab079a3a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt
@@ -11,10 +11,8 @@ import android.view.View
import android.widget.RemoteViews
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.Resource
-import io.github.wulkanowy.data.dataOrNull
+import io.github.wulkanowy.data.dataOrThrow
import io.github.wulkanowy.data.db.SharedPrefProvider
-import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.toFirstResult
@@ -69,8 +67,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
appWidgetIds?.forEach { widgetId ->
val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0)
- val luckyNumberResource = getLuckyNumber(studentId, widgetId)
- val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber?.toString()
+ val luckyNumber = getLuckyNumber(studentId, widgetId)?.luckyNumber?.toString()
val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber)
.apply {
setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-")
@@ -143,18 +140,18 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id)
}
}
+
else -> null
}
if (currentStudent != null) {
luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false)
.toFirstResult()
- } else {
- Resource.Success(null)
- }
+ .dataOrThrow
+ } else null
} catch (e: Exception) {
Timber.e(e, "An error has occurred in lucky number provider")
- Resource.Error(e)
+ null
}
}
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 25ab73bca..62c16257e 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
@@ -9,9 +9,14 @@ import android.view.MenuItem
import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.OnBackPressedCallback
import androidx.activity.addCallback
-import androidx.core.view.*
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -23,16 +28,32 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.ActivityMainBinding
-import io.github.wulkanowy.databinding.DialogAdsConsentBinding
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
+import io.github.wulkanowy.ui.modules.auth.AuthDialog
+import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AnalyticsHelper
+import io.github.wulkanowy.utils.AppInfo
+import io.github.wulkanowy.utils.InAppReviewHelper
+import io.github.wulkanowy.utils.InAppUpdateHelper
+import io.github.wulkanowy.utils.createNameInitialsDrawable
+import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.nickOrName
+import io.github.wulkanowy.utils.safelyPopFragments
+import io.github.wulkanowy.utils.setOnViewChangeListener
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import timber.log.Timber
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
@AndroidEntryPoint
class MainActivity : BaseActivity(), MainView,
@@ -62,6 +83,8 @@ class MainActivity : BaseActivity(), MainVie
private val navController =
FragNavController(supportFragmentManager, R.id.main_fragment_container)
+ private val captchaVerificationEvent = MutableSharedFlow()
+
companion object {
private const val EXTRA_START_DESTINATION = "start_destination_json"
@@ -133,6 +156,7 @@ class MainActivity : BaseActivity(), MainVie
initializeToolbar()
initializeBottomNavigation(startMenuIndex, rootAppMenuItems)
initializeNavController(startMenuIndex, rootUpdatedDestinations)
+ initializeCaptchaVerificationEvent()
}
private fun initializeNavController(
@@ -312,38 +336,25 @@ class MainActivity : BaseActivity(), MainVie
.show()
}
- override fun showPrivacyPolicyDialog() {
- val dialogAdsConsentBinding = DialogAdsConsentBinding.inflate(layoutInflater)
-
- val dialog = MaterialAlertDialogBuilder(this)
- .setTitle(R.string.pref_ads_consent_title)
- .setMessage(R.string.pref_ads_consent_description)
- .setView(dialogAdsConsentBinding.root)
- .show()
-
- dialogAdsConsentBinding.adsConsentOver.setOnCheckedChangeListener { _, isChecked ->
- dialogAdsConsentBinding.adsConsentPersonalised.isEnabled = isChecked
- }
-
- dialogAdsConsentBinding.adsConsentPersonalised.setOnClickListener {
- presenter.onPrivacyAgree(true)
- dialog.dismiss()
- }
-
- dialogAdsConsentBinding.adsConsentNonPersonalised.setOnClickListener {
- presenter.onPrivacyAgree(false)
- dialog.dismiss()
- }
-
- dialogAdsConsentBinding.adsConsentPrivacy.setOnClickListener { presenter.onPrivacySelected() }
- dialogAdsConsentBinding.adsConsentCancel.setOnClickListener { dialog.cancel() }
+ @OptIn(FlowPreview::class)
+ private fun initializeCaptchaVerificationEvent() {
+ captchaVerificationEvent
+ .debounce(1.seconds)
+ .onEach { url ->
+ Timber.d("Showing captcha dialog for: $url")
+ showDialogFragment(CaptchaDialog.newInstance(url))
+ }
+ .launchIn(lifecycleScope)
}
- override fun openPrivacyPolicy() {
- openInternetBrowser(
- "https://wulkanowy.github.io/polityka-prywatnosci.html",
- ::showMessage
- )
+ override fun onCaptchaVerificationRequired(url: String?) {
+ lifecycleScope.launch {
+ captchaVerificationEvent.emit(url)
+ }
+ }
+
+ override fun showAuthDialog() {
+ showDialogFragment(AuthDialog.newInstance())
}
override fun onSaveInstanceState(outState: Bundle) {
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 ae05ecf22..5469fcad3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
@@ -19,7 +19,6 @@ import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.launch
-import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import timber.log.Timber
import java.time.Duration
@@ -52,6 +51,7 @@ class MainPresenter @Inject constructor(
destinationType in rootDestinationTypeList -> {
rootDestinationTypeList.indexOf(destinationType)
}
+
else -> 4
}
@@ -110,6 +110,7 @@ class MainPresenter @Inject constructor(
is AccountView,
is StudentInfoView,
is AccountDetailsView -> false
+
else -> true
}
@@ -148,20 +149,8 @@ class MainPresenter @Inject constructor(
}
fun onEnableAdsSelected() {
- view?.showPrivacyPolicyDialog()
- }
-
- fun onPrivacyAgree(isPersonalizedAds: Boolean) {
- preferencesRepository.isAgreeToProcessData = true
- preferencesRepository.isPersonalizedAdsEnabled = isPersonalizedAds
-
- adsHelper.initialize()
-
preferencesRepository.isAdsEnabled = true
- }
-
- fun onPrivacySelected() {
- view?.openPrivacyPolicy()
+ adsHelper.initialize()
}
private fun checkInAppReview() {
@@ -189,8 +178,8 @@ class MainPresenter @Inject constructor(
.getOrElse { return@launch }
if (Instant.now().minus(Duration.ofDays(28)).isAfter(student.registrationDate)) {
- view?.showAppSupport()
preferencesRepository.isAppSupportShown = true
+ view?.showAppSupport()
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
index 62436f3bf..70a94fc81 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
@@ -46,10 +46,6 @@ interface MainView : BaseView {
fun showAppSupport()
- fun showPrivacyPolicyDialog()
-
- fun openPrivacyPolicy()
-
fun openMoreDestination(destination: Destination)
interface MainChildView {
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 d3c6b95c7..b83f7e232 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
@@ -50,12 +50,15 @@ class MessagePreviewAdapter @Inject constructor() :
ViewType.MESSAGE.id -> MessageViewHolder(
ItemMessagePreviewBinding.inflate(inflater, parent, false)
)
+
ViewType.DIVIDER.id -> DividerViewHolder(
ItemMessageDividerBinding.inflate(inflater, parent, false)
)
+
ViewType.ATTACHMENT.id -> AttachmentViewHolder(
ItemMessageAttachmentBinding.inflate(inflater, parent, false)
)
+
else -> throw IllegalStateException()
}
}
@@ -66,6 +69,7 @@ class MessagePreviewAdapter @Inject constructor() :
holder,
requireNotNull(messageWithAttachment).message
)
+
is AttachmentViewHolder -> bindAttachment(
holder,
requireNotNull(messageWithAttachment).attachments[position - 2]
@@ -82,9 +86,11 @@ class MessagePreviewAdapter @Inject constructor() :
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))
}
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 3ed685cd7..75778bac5 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
@@ -44,18 +44,33 @@ class MessagePreviewFragment :
private var menuForwardButton: MenuItem? = null
+ private var menuRestoreButton: MenuItem? = null
+
private var menuDeleteButton: MenuItem? = null
+ private var menuDeleteForeverButton: MenuItem? = null
+
private var menuShareButton: MenuItem? = null
private var menuPrintButton: MenuItem? = null
+ private var menuMuteButton: MenuItem? = null
+
override val titleStringId: Int
get() = R.string.message_title
override val deleteMessageSuccessString: String
get() = getString(R.string.message_delete_success)
+ override val muteMessageSuccessString: String
+ get() = getString(R.string.message_mute_success)
+
+ override val unmuteMessageSuccessString: String
+ get() = getString(R.string.message_unmute_success)
+
+ override val restoreMessageSuccessString: String
+ get() = getString(R.string.message_restore_success)
+
override val messageNoSubjectString: String
get() = getString(R.string.message_no_subject)
@@ -103,9 +118,12 @@ class MessagePreviewFragment :
inflater.inflate(R.menu.action_menu_message_preview, menu)
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
+ menuRestoreButton = menu.findItem(R.id.messagePreviewMenuRestore)
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
+ menuDeleteForeverButton = menu.findItem(R.id.messagePreviewMenuDeleteForever)
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
+ menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute)
presenter.onCreateOptionsMenu()
menu.findItem(R.id.mainMenuAccount).isVisible = false
@@ -115,9 +133,12 @@ class MessagePreviewFragment :
return when (item.itemId) {
R.id.messagePreviewMenuReply -> presenter.onReply()
R.id.messagePreviewMenuForward -> presenter.onForward()
+ R.id.messagePreviewMenuRestore -> presenter.onMessageRestore()
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
+ R.id.messagePreviewMenuDeleteForever -> presenter.onMessageDelete()
R.id.messagePreviewMenuShare -> presenter.onShare()
R.id.messagePreviewMenuPrint -> presenter.onPrint()
+ R.id.messagePreviewMenuMute -> presenter.onMute()
else -> false
}
}
@@ -129,6 +150,11 @@ class MessagePreviewFragment :
}
}
+ override fun updateMuteToggleButton(isMuted: Boolean) {
+ menuMuteButton?.setTitle(if (isMuted) R.string.message_unmute else R.string.message_mute)
+
+ }
+
override fun showProgress(show: Boolean) {
binding.messagePreviewProgress.visibility = if (show) VISIBLE else GONE
}
@@ -137,20 +163,15 @@ class MessagePreviewFragment :
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
}
- override fun showOptions(show: Boolean, isReplayable: Boolean) {
- menuReplyButton?.isVisible = isReplayable
+ override fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) {
+ menuReplyButton?.isVisible = show && isReplayable
menuForwardButton?.isVisible = show
- menuDeleteButton?.isVisible = show
+ menuRestoreButton?.isVisible = show && isRestorable
+ menuDeleteButton?.isVisible = show && !isRestorable
+ menuDeleteForeverButton?.isVisible = show && isRestorable
menuShareButton?.isVisible = show
menuPrintButton?.isVisible = show
- }
-
- override fun setDeletedOptionsLabels() {
- menuDeleteButton?.setTitle(R.string.message_delete_forever)
- }
-
- override fun setNotDeletedOptionsLabels() {
- menuDeleteButton?.setTitle(R.string.message_move_to_trash)
+ menuMuteButton?.isVisible = show && isReplayable
}
override fun showErrorView(show: Boolean) {
@@ -213,7 +234,7 @@ class MessagePreviewFragment :
}
override fun onSaveInstanceState(outState: Bundle) {
- outState.putSerializable(MESSAGE_ID_KEY, presenter.message)
+ outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments)
super.onSaveInstanceState(outState)
}
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 cd7b72843..9bb0d32a4 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
@@ -5,7 +5,7 @@ import androidx.core.text.parseAsHtml
import io.github.wulkanowy.R
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.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
@@ -14,9 +14,11 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.toFormattedString
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
class MessagePreviewPresenter @Inject constructor(
errorHandler: ErrorHandler,
@@ -26,9 +28,7 @@ class MessagePreviewPresenter @Inject constructor(
private val analytics: AnalyticsHelper
) : BasePresenter(errorHandler, studentRepository) {
- var message: Message? = null
-
- var attachments: List? = null
+ var messageWithAttachments: MessageWithAttachment? = null
private lateinit var lastError: Throwable
@@ -38,7 +38,6 @@ class MessagePreviewPresenter @Inject constructor(
super.onAttachView(view)
view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError
- this.message = message
loadData(requireNotNull(message))
}
@@ -66,25 +65,24 @@ class MessagePreviewPresenter @Inject constructor(
.logResourceStatus("message ${messageToLoad.messageId} preview")
.onResourceData {
if (it != null) {
- message = it.message
- attachments = it.attachments
+ messageWithAttachments = it
view?.apply {
setMessageWithAttachment(it)
showContent(true)
initOptions()
-
+ updateMuteToggleButton(isMuted = it.mutedMessageSender != null)
if (preferencesRepository.isIncognitoMode && it.message.unread) {
showMessage(R.string.message_incognito_description)
}
}
} else {
+ delay(1.seconds)
view?.run {
showMessage(messageNotExists)
popView()
}
}
- }
- .onResourceSuccess {
+ }.onResourceSuccess {
if (it != null) {
analytics.logEvent(
"load_item",
@@ -92,31 +90,28 @@ class MessagePreviewPresenter @Inject constructor(
"length" to it.message.content.length
)
}
- }
- .onResourceNotLoading { view?.showProgress(false) }
- .onResourceError {
+ }.onResourceNotLoading { view?.showProgress(false) }.onResourceError {
retryCallback = { onMessageLoadRetry(messageToLoad) }
errorHandler.dispatch(it)
- }
- .launch()
+ }.launch()
}
fun onReply(): Boolean {
- return if (message != null) {
- view?.openMessageReply(message)
+ return if (messageWithAttachments?.message != null) {
+ view?.openMessageReply(messageWithAttachments?.message)
true
} else false
}
fun onForward(): Boolean {
- return if (message != null) {
- view?.openMessageForward(message)
+ return if (messageWithAttachments?.message != null) {
+ view?.openMessageForward(messageWithAttachments?.message)
true
} else false
}
fun onShare(): Boolean {
- val message = message ?: return false
+ val message = messageWithAttachments?.message ?: return false
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
val text = buildString {
@@ -129,13 +124,15 @@ class MessagePreviewPresenter @Inject constructor(
appendLine(message.content.parseAsHtml())
- if (!attachments.isNullOrEmpty()) {
+ if (!messageWithAttachments?.attachments.isNullOrEmpty()) {
appendLine()
appendLine("Załączniki:")
- append(attachments.orEmpty().joinToString(separator = "\n") { attachment ->
- "${attachment.filename}: ${attachment.url}"
- })
+ append(
+ messageWithAttachments?.attachments.orEmpty()
+ .joinToString(separator = "\n") { attachment ->
+ "${attachment.filename}: ${attachment.url}"
+ })
}
}
@@ -148,7 +145,7 @@ class MessagePreviewPresenter @Inject constructor(
@SuppressLint("NewApi")
fun onPrint(): Boolean {
- val message = message ?: return false
+ val message = messageWithAttachments?.message ?: return false
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
val dateString = message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
@@ -159,8 +156,7 @@ class MessagePreviewPresenter @Inject constructor(
append("Od
${message.sender}
")
append("DO
${message.recipients}")
}
- val messageContent = "${message.content}
"
- .replace(Regex("[\\n\\r]{2,}"), "")
+ val messageContent = "
${message.content}
".replace(Regex("[\\n\\r]{2,}"), "")
.replace(Regex("[\\n\\r]"), "
")
val jobName = buildString {
@@ -171,9 +167,7 @@ class MessagePreviewPresenter @Inject constructor(
}
view?.apply {
- val html = printHTML
- .replace("%SUBJECT%", subject)
- .replace("%CONTENT%", messageContent)
+ val html = printHTML.replace("%SUBJECT%", subject).replace("%CONTENT%", messageContent)
.replace("%INFO%", infoContent)
printDocument(html, jobName)
}
@@ -181,34 +175,69 @@ class MessagePreviewPresenter @Inject constructor(
return true
}
- private fun deleteMessage() {
- message ?: return
+ private fun restoreMessage() {
+ val message = messageWithAttachments?.message ?: return
view?.run {
showContent(false)
showProgress(true)
- showOptions(show = false, isReplayable = false)
+ showOptions(
+ show = false,
+ isReplayable = false,
+ isRestorable = false,
+ )
showErrorView(false)
}
-
- Timber.i("Delete message ${message?.messageGlobalKey}")
-
+ Timber.i("Restore message ${message.messageGlobalKey}")
presenterScope.launch {
runCatching {
val student = studentRepository.getCurrentStudent(decryptPass = true)
val mailbox = messageRepository.getMailboxByStudent(student)
- messageRepository.deleteMessage(student, mailbox, message!!)
+ messageRepository.restoreMessages(student, mailbox, listOfNotNull(message))
}
.onFailure {
- retryCallback = { onMessageDelete() }
+ retryCallback = { onMessageRestore() }
errorHandler.dispatch(it)
}
.onSuccess {
view?.run {
- showMessage(deleteMessageSuccessString)
+ showMessage(restoreMessageSuccessString)
popView()
}
}
+ view?.showProgress(false)
+ }
+ }
+
+ private fun deleteMessage() {
+ messageWithAttachments?.message ?: return
+
+ view?.run {
+ showContent(false)
+ showProgress(true)
+ showOptions(
+ show = false,
+ isReplayable = false,
+ isRestorable = false,
+ )
+ showErrorView(false)
+ }
+
+ Timber.i("Delete message ${messageWithAttachments?.message?.messageGlobalKey}")
+
+ presenterScope.launch {
+ runCatching {
+ val student = studentRepository.getCurrentStudent(decryptPass = true)
+ messageRepository.deleteMessage(student, messageWithAttachments?.message!!)
+ }.onFailure {
+ retryCallback = { onMessageDelete() }
+ errorHandler.dispatch(it)
+ }.onSuccess {
+ view?.run {
+ showMessage(deleteMessageSuccessString)
+ popView()
+ }
+ }
view?.showProgress(false)
}
@@ -224,6 +253,11 @@ class MessagePreviewPresenter @Inject constructor(
}
}
+ fun onMessageRestore(): Boolean {
+ restoreMessage()
+ return true
+ }
+
fun onMessageDelete(): Boolean {
deleteMessage()
return true
@@ -232,20 +266,39 @@ class MessagePreviewPresenter @Inject constructor(
private fun initOptions() {
view?.apply {
showOptions(
- show = message != null,
- isReplayable = message?.folderId != MessageFolder.SENT.id,
+ show = messageWithAttachments?.message != null,
+ isReplayable = messageWithAttachments?.message?.folderId == MessageFolder.RECEIVED.id,
+ isRestorable = messageWithAttachments?.message?.folderId == MessageFolder.TRASHED.id,
)
- message?.let {
- when (it.folderId == MessageFolder.TRASHED.id) {
- true -> setDeletedOptionsLabels()
- false -> setNotDeletedOptionsLabels()
- }
- }
-
}
}
fun onCreateOptionsMenu() {
initOptions()
}
+
+ fun onMute(): Boolean {
+ val message = messageWithAttachments?.message ?: return false
+ val isMuted = messageWithAttachments?.mutedMessageSender != null
+
+ presenterScope.launch {
+ runCatching {
+ when (isMuted) {
+ true -> {
+ messageRepository.unmuteMessage(message.correspondents)
+ view?.run { showMessage(unmuteMessageSuccessString) }
+ }
+
+ false -> {
+ messageRepository.muteMessage(message.correspondents)
+ view?.run { showMessage(muteMessageSuccessString) }
+ }
+ }
+ }.onFailure {
+ errorHandler.dispatch(it)
+ }
+ }
+ view?.updateMuteToggleButton(isMuted)
+ return true
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
index 7f5f140b2..ee0b6ce0a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
@@ -9,6 +9,12 @@ interface MessagePreviewView : BaseView {
val deleteMessageSuccessString: String
+ val muteMessageSuccessString: String
+
+ val unmuteMessageSuccessString: String
+
+ val restoreMessageSuccessString: String
+
val messageNoSubjectString: String
val printHTML: String
@@ -19,6 +25,8 @@ interface MessagePreviewView : BaseView {
fun setMessageWithAttachment(item: MessageWithAttachment)
+ fun updateMuteToggleButton(isMuted: Boolean)
+
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
@@ -29,11 +37,7 @@ interface MessagePreviewView : BaseView {
fun setErrorRetryCallback(callback: () -> Unit)
- fun showOptions(show: Boolean, isReplayable: Boolean)
-
- fun setDeletedOptionsLabels()
-
- fun setNotDeletedOptionsLabels()
+ fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean)
fun openMessageReply(message: Message?)
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 e776e9941..6155baea3 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
@@ -203,7 +203,7 @@ class SendMessagePresenter @Inject constructor(
subject = subject,
content = content,
recipients = recipients,
- mailboxId = mailbox.globalKey,
+ mailbox = mailbox,
)
}.logResourceStatus("sending message").onEach {
when (it) {
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 9792c7085..fadc77e6d 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
@@ -18,8 +18,7 @@ import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
-class MessageTabAdapter @Inject constructor() :
- RecyclerView.Adapter() {
+class MessageTabAdapter @Inject constructor() : RecyclerView.Adapter() {
lateinit var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit
@@ -52,10 +51,11 @@ class MessageTabAdapter @Inject constructor() :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
- return when (MessageItemViewType.values()[viewType]) {
+ return when (MessageItemViewType.entries[viewType]) {
MessageItemViewType.FILTERS -> HeaderViewHolder(
ItemMessageChipsBinding.inflate(inflater, parent, false)
)
+
MessageItemViewType.MESSAGE -> ItemViewHolder(
ItemMessageBinding.inflate(inflater, parent, false)
)
@@ -137,7 +137,12 @@ class MessageTabAdapter @Inject constructor() :
ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(currentTextColor))
isVisible = message.hasAttachments
}
- messageItemUnreadIndicator.isVisible = message.unread
+ messageItemUnreadIndicator.isVisible = message.unread || item.isMuted
+
+ when (item.isMuted) {
+ true -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_notifications_off)
+ else -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_circle_notification)
+ }
root.setOnClickListener {
holder.bindingAdapterPosition.let {
@@ -165,8 +170,7 @@ class MessageTabAdapter @Inject constructor() :
RecyclerView.ViewHolder(binding.root)
private class MessageTabDiffUtil(
- private val old: List,
- private val new: List
+ private val old: List, private val new: List
) : DiffUtil.Callback() {
override fun getOldListSize(): Int = old.size
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 c0bd4170e..ef640e040 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
@@ -6,6 +6,7 @@ sealed class MessageTabDataItem(val viewType: MessageItemViewType) {
data class MessageItem(
val message: Message,
+ val isMuted: Boolean,
val isSelected: Boolean,
val isActionMode: Boolean
) : MessageTabDataItem(MessageItemViewType.MESSAGE)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt
index 4364e8681..12f9d3234 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
@@ -5,7 +5,9 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
-import android.view.View.*
+import android.view.View.GONE
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
import android.widget.CompoundButton
import androidx.annotation.StringRes
import androidx.appcompat.view.ActionMode
@@ -64,10 +66,12 @@ class MessageTabFragment : BaseFragment(R.layout.frag
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
- if (presenter.folder == MessageFolder.TRASHED) {
- val menuItem = menu.findItem(R.id.messageTabContextMenuDelete)
- menuItem.setTitle(R.string.message_delete_forever)
- }
+ val isTrashFolder = presenter.folder == MessageFolder.TRASHED
+
+ menu.findItem(R.id.messageTabContextMenuDelete).setVisible(!isTrashFolder)
+ menu.findItem(R.id.messageTabContextMenuDeleteForever).setVisible(isTrashFolder)
+ menu.findItem(R.id.messageTabContextMenuRestore).setVisible(isTrashFolder)
+
return presenter.onPrepareActionMode()
}
@@ -79,6 +83,8 @@ class MessageTabFragment : BaseFragment(R.layout.frag
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
when (menu.itemId) {
R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete()
+ R.id.messageTabContextMenuRestore -> presenter.onActionModeSelectRestore()
+ R.id.messageTabContextMenuDeleteForever -> presenter.onActionModeSelectDelete()
R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll()
}
return true
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 90f93b145..cda0b32bd 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
@@ -4,6 +4,7 @@ import io.github.wulkanowy.R
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.db.entities.MessageWithMutedAuthor
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.StudentRepository
@@ -39,7 +40,7 @@ class MessageTabPresenter @Inject constructor(
private var mailboxes: List = emptyList()
private var selectedMailbox: Mailbox? = null
- private var messages = emptyList()
+ private var messages = emptyList()
private val searchChannel = Channel()
@@ -120,8 +121,27 @@ class MessageTabPresenter @Inject constructor(
return true
}
+ fun onActionModeSelectRestore() {
+ Timber.i("Restore ${messagesToDelete.size} messages")
+ val messageList = messagesToDelete.toList()
+
+ presenterScope.launch {
+ view?.run {
+ showProgress(true)
+ showContent(false)
+ showActionMode(false)
+ }
+ runCatching {
+ val student = studentRepository.getCurrentStudent(true)
+ messageRepository.restoreMessages(student, selectedMailbox, messageList)
+ }
+ .onFailure(errorHandler::dispatch)
+ .onSuccess { view?.showMessage(R.string.message_messages_restored) }
+ }
+ }
+
fun onActionModeSelectDelete() {
- Timber.i("Delete ${messagesToDelete.size} messages)")
+ Timber.i("Delete ${messagesToDelete.size} messages")
val messageList = messagesToDelete.toList()
presenterScope.launch {
@@ -133,7 +153,7 @@ class MessageTabPresenter @Inject constructor(
runCatching {
val student = studentRepository.getCurrentStudent(true)
- messageRepository.deleteMessages(student, selectedMailbox, messageList)
+ messageRepository.deleteMessages(student, messageList)
}
.onFailure(errorHandler::dispatch)
.onSuccess { view?.showMessage(R.string.message_messages_deleted) }
@@ -141,7 +161,7 @@ class MessageTabPresenter @Inject constructor(
}
fun onActionModeSelectCheckAll() {
- val messagesToSelect = getFilteredData()
+ val messagesToSelect = getFilteredData().map { it.message }
val isAllSelected = messagesToDelete.containsAll(messagesToSelect)
if (isAllSelected) {
@@ -188,7 +208,7 @@ class MessageTabPresenter @Inject constructor(
view?.showActionMode(false)
}
- val filteredData = getFilteredData()
+ val filteredData = getFilteredData().map { it.message }
view?.run {
updateActionModeTitle(messagesToDelete.size)
@@ -320,25 +340,31 @@ class MessageTabPresenter @Inject constructor(
}
}
- private fun getFilteredData(): List {
+ private fun getFilteredData(): List {
if (lastSearchQuery.trim().isEmpty()) {
- val sortedMessages = messages.sortedByDescending { it.date }
+ val sortedMessages = messages.sortedByDescending { it.message.date }
return when {
- (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
- (onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread }
- onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments }
+ (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter {
+ it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments
+ }
+
+ (onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread }
+ onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments }
else -> sortedMessages
}
} else {
val sortedMessages = messages
- .map { it to calculateMatchRatio(it, lastSearchQuery) }
- .sortedWith(compareBy> { -it.second }.thenByDescending { it.first.date })
+ .map { it to calculateMatchRatio(it.message, lastSearchQuery) }
+ .sortedWith(compareBy> { -it.second }.thenByDescending { it.first.message.date })
.filter { it.second > 6000 }
.map { it.first }
return when {
- (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
- (onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread }
- onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments }
+ (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter {
+ it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments
+ }
+
+ (onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread }
+ onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments }
else -> sortedMessages
}
}
@@ -367,8 +393,9 @@ class MessageTabPresenter @Inject constructor(
addAll(data.map { message ->
MessageTabDataItem.MessageItem(
- message = message,
- isSelected = messagesToDelete.any { it.messageGlobalKey == message.messageGlobalKey },
+ message = message.message,
+ isMuted = message.mutedMessageSender != null,
+ isSelected = messagesToDelete.any { it.messageGlobalKey == message.message.messageGlobalKey },
isActionMode = isActionMode
)
})
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
index 21f564988..f8d1323c6 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.settings
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import io.github.wulkanowy.R
+import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.main.MainView
import timber.log.Timber
@@ -24,7 +25,11 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, Settin
override fun showMessage(text: String) {}
- override fun showExpiredDialog() {}
+ override fun showExpiredCredentialsDialog() {}
+
+ override fun onCaptchaVerificationRequired(url: String?) = Unit
+
+ override fun showDecryptionFailedDialog() {}
override fun openClearLoginView() {}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
index 1b8d1a8fa..3ef1a80a3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
@@ -8,7 +8,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject
@@ -47,8 +46,16 @@ class AdvancedFragment : PreferenceFragmentCompat(),
(activity as? BaseActivity<*, *>)?.showMessage(text)
}
- override fun showExpiredDialog() {
- (activity as? BaseActivity<*, *>)?.showExpiredDialog()
+ override fun showExpiredCredentialsDialog() {
+ (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
+ }
+
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
+ override fun showDecryptionFailedDialog() {
+ (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
override fun showChangePasswordSnackbar(redirectUrl: String) {
@@ -64,7 +71,7 @@ class AdvancedFragment : PreferenceFragmentCompat(),
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun onResume() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
index 70dd694cc..3d0c8052b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
@@ -9,7 +9,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject
@@ -63,8 +62,16 @@ class AppearanceFragment : PreferenceFragmentCompat(),
(activity as? BaseActivity<*, *>)?.showMessage(text)
}
- override fun showExpiredDialog() {
- (activity as? BaseActivity<*, *>)?.showExpiredDialog()
+ override fun showExpiredCredentialsDialog() {
+ (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
+ }
+
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
+ override fun showDecryptionFailedDialog() {
+ (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
override fun showChangePasswordSnackbar(redirectUrl: String) {
@@ -80,7 +87,7 @@ class AppearanceFragment : PreferenceFragmentCompat(),
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun onResume() {
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 af4c4e6ae..0bf9ddadd 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
@@ -21,7 +21,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openInternetBrowser
@@ -133,8 +132,16 @@ class NotificationsFragment : PreferenceFragmentCompat(),
(activity as? BaseActivity<*, *>)?.showMessage(text)
}
- override fun showExpiredDialog() {
- (activity as? BaseActivity<*, *>)?.showExpiredDialog()
+ override fun showExpiredCredentialsDialog() {
+ (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
+ }
+
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
+ override fun showDecryptionFailedDialog() {
+ (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
override fun showChangePasswordSnackbar(redirectUrl: String) {
@@ -150,7 +157,7 @@ class NotificationsFragment : PreferenceFragmentCompat(),
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun showFixSyncDialog() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
index f48abe9ba..d57144832 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
@@ -10,7 +10,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
-import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
import javax.inject.Inject
@@ -84,8 +83,16 @@ class SyncFragment : PreferenceFragmentCompat(),
}
}
- override fun showExpiredDialog() {
- (activity as? BaseActivity<*, *>)?.showExpiredDialog()
+ override fun showExpiredCredentialsDialog() {
+ (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
+ }
+
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
+ override fun showDecryptionFailedDialog() {
+ (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
override fun showChangePasswordSnackbar(redirectUrl: String) {
@@ -101,7 +108,7 @@ class SyncFragment : PreferenceFragmentCompat(),
}
override fun showAuthDialog() {
- AuthDialog.newInstance().show(childFragmentManager, "auth_dialog")
+ (activity as? BaseActivity<*, *>)?.showAuthDialog()
}
override fun onResume() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
index 6b442d1c2..e83f25176 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
@@ -2,9 +2,7 @@ package io.github.wulkanowy.ui.modules.timetable
import android.os.Handler
import android.os.Looper
-import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.Semester
-import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS
import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS
@@ -20,8 +18,8 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
-import io.github.wulkanowy.data.toFirstResult
-import io.github.wulkanowy.data.waitForResult
+import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
+import io.github.wulkanowy.domain.timetable.IsWeekendHasLessonsUseCase
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
@@ -31,16 +29,12 @@ import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.isJustFinished
import io.github.wulkanowy.utils.isShowTimeUntil
import io.github.wulkanowy.utils.left
-import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay
-import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.until
-import kotlinx.coroutines.flow.firstOrNull
import timber.log.Timber
-import java.time.DayOfWeek
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDate.now
@@ -54,6 +48,8 @@ class TimetablePresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val timetableRepository: TimetableRepository,
+ private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase,
+ private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase,
private val semesterRepository: SemesterRepository,
private val prefRepository: PreferencesRepository,
private val analytics: AnalyticsHelper,
@@ -153,7 +149,7 @@ class TimetablePresenter @Inject constructor(
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
- checkInitialAndCurrentDate(student, semester)
+ checkInitialAndCurrentDate(semester)
timetableRepository.getTimetable(
student = student,
semester = semester,
@@ -165,7 +161,7 @@ class TimetablePresenter @Inject constructor(
}
.logResourceStatus("load timetable data")
.onResourceData {
- isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessons(it.lessons)
+ isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessonsUseCase(it.lessons)
view?.run {
enableSwipe(true)
@@ -197,17 +193,9 @@ class TimetablePresenter @Inject constructor(
.launch()
}
- private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
+ private suspend fun checkInitialAndCurrentDate(semester: Semester) {
if (initialDate == null) {
- val lessons = timetableRepository.getTimetable(
- student = student,
- semester = semester,
- start = now().monday,
- end = now().sunday,
- forceRefresh = false,
- timetableType = TimetableRepository.TimetableType.NORMAL
- ).toFirstResult().dataOrNull?.lessons.orEmpty()
- isWeekendHasLessons = isWeekendHasLessons(lessons)
+ isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(semester)
initialDate = getInitialDate(semester)
}
@@ -216,15 +204,6 @@ class TimetablePresenter @Inject constructor(
}
}
- private fun isWeekendHasLessons(
- lessons: List,
- ): Boolean = lessons.any {
- it.date.dayOfWeek in listOf(
- DayOfWeek.SATURDAY,
- DayOfWeek.SUNDAY,
- )
- }
-
private fun getInitialDate(semester: Semester): LocalDate {
val now = now()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt
index 4e0578e2b..4cfc03229 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt
@@ -11,7 +11,7 @@ import android.view.View.VISIBLE
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.dataOrNull
+import io.github.wulkanowy.data.dataOrThrow
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
@@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Co
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey
import io.github.wulkanowy.utils.getCompatColor
+import io.github.wulkanowy.utils.getErrorString
import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.runBlocking
@@ -67,25 +68,31 @@ class TimetableWidgetFactory(
override fun onDestroy() {}
override fun onDataSetChanged() {
- intent?.extras?.getInt(EXTRA_APPWIDGET_ID)?.let { appWidgetId ->
- val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0))
- val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0)
+ val appWidgetId = intent?.extras?.getInt(EXTRA_APPWIDGET_ID) ?: return
+ val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0))
+ val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0)
+ items = emptyList()
+
+ runBlocking {
runCatching {
- runBlocking {
- val student = getStudent(studentId) ?: return@runBlocking
- val semester = semesterRepository.getCurrentSemester(student)
- items = createItems(
- lessons = getLessons(student, semester, date),
- lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
- )
+ val student = getStudent(studentId) ?: return@runBlocking
+ val semester = semesterRepository.getCurrentSemester(student)
+ val lessons = getLessons(student, semester, date)
+ val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
+
+ createItems(lessons, lastSync)
+ }
+ .onFailure {
+ items = listOf(TimetableWidgetItem.Error(it))
+ Timber.e(it, "An error has occurred in timetable widget factory")
+ }
+ .onSuccess {
+ items = it
if (date == LocalDate.now()) {
updateTodayLastLessonEnd(appWidgetId)
}
}
- }.onFailure {
- Timber.e(it, "An error has occurred in timetable widget factory")
- }
}
}
@@ -98,7 +105,7 @@ class TimetableWidgetFactory(
student: Student, semester: Semester, date: LocalDate
): List {
val timetable = timetableRepository.getTimetable(student, semester, date, date, false)
- val lessons = timetable.toFirstResult().dataOrNull?.lessons.orEmpty()
+ val lessons = timetable.toFirstResult().dataOrThrow.lessons
return lessons.sortedBy { it.number }
}
@@ -110,6 +117,7 @@ class TimetableWidgetFactory(
BETWEEN_AND_BEFORE_LESSONS -> 0
else -> null
}
+
return buildList {
lessons.forEach {
if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) {
@@ -133,15 +141,12 @@ class TimetableWidgetFactory(
sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true)
}
- companion object {
- const val TIME_FORMAT_STYLE = "HH:mm"
- }
-
override fun getViewAt(position: Int): RemoteViews? {
return when (val item = items.getOrNull(position) ?: return null) {
is TimetableWidgetItem.Normal -> getNormalItemRemoteView(item)
is TimetableWidgetItem.Empty -> getEmptyItemRemoteView(item)
is TimetableWidgetItem.Synchronized -> getSynchronizedItemRemoteView(item)
+ is TimetableWidgetItem.Error -> getErrorItemRemoteView(item)
}
}
@@ -213,6 +218,18 @@ class TimetableWidgetFactory(
}
}
+ private fun getErrorItemRemoteView(item: TimetableWidgetItem.Error): RemoteViews {
+ return RemoteViews(
+ context.packageName,
+ R.layout.item_widget_timetable_error
+ ).apply {
+ setTextViewText(
+ R.id.timetable_widget_item_error_message,
+ context.resources.getErrorString(item.error)
+ )
+ }
+ }
+
private fun updateTheme() {
when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> {
@@ -300,4 +317,8 @@ class TimetableWidgetFactory(
synchronizationTime,
)
}
+
+ private companion object {
+ private const val TIME_FORMAT_STYLE = "HH:mm"
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt
index 166b1a8fb..fb02f8919 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt
@@ -17,10 +17,15 @@ sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) {
data class Synchronized(
val timestamp: Instant,
) : TimetableWidgetItem(TimetableWidgetItemType.SYNCHRONIZED)
+
+ data class Error(
+ val error: Throwable
+ ) : TimetableWidgetItem(TimetableWidgetItemType.ERROR)
}
enum class TimetableWidgetItemType {
NORMAL,
EMPTY,
SYNCHRONIZED,
+ ERROR,
}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
index 397c95953..19d0929db 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
@@ -19,6 +19,9 @@ private inline val AttendanceSummary.allAbsences: Double
inline val Attendance.isExcusableOrNotExcused: Boolean
get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null
+inline val Attendance.isAbsenceExcusable: Boolean
+ get() = (excusable && absence && !excused) && excuseStatus == null
+
fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences)
fun List.calculatePercentage(): Double {
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 a4c2537ac..1c2290510 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
@@ -3,6 +3,8 @@ package io.github.wulkanowy.utils
import android.content.res.Resources
import io.github.wulkanowy.R
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
+import io.github.wulkanowy.sdk.scrapper.exception.AccountInactiveException
+import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException
@@ -32,8 +34,10 @@ fun Resources.getErrorString(error: Throwable): String = when (error) {
is ServiceUnavailableException -> R.string.error_service_unavailable
is FeatureDisabledException -> R.string.error_feature_disabled
is FeatureNotAvailableException -> R.string.error_feature_not_available
+ is AccountInactiveException -> R.string.error_account_inactive
is VulcanException -> R.string.error_unknown_uonet
is ScrapperException -> R.string.error_unknown_app
+ is CloudflareVerificationException -> R.string.error_cloudflare_captcha
is SSLHandshakeException -> when {
error.isCausedByCertificateNotValidNow() -> R.string.error_invalid_device_datetime
else -> R.string.error_timeout
diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
index 889d64ea1..9b6ca7060 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
@@ -1,5 +1,6 @@
package io.github.wulkanowy.utils
+import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.Sdk
import timber.log.Timber
@@ -11,6 +12,7 @@ fun Sdk.init(student: Student): Sdk {
schoolSymbol = student.schoolSymbol
studentId = student.studentId
classId = student.classId
+ emptyCookieJarInterceptor = true
if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
mobileBaseUrl = student.mobileBaseUrl
@@ -29,3 +31,12 @@ fun Sdk.init(student: Student): Sdk {
return this
}
+
+fun Sdk.switchSemester(semester: Semester): Sdk {
+ return switchDiary(
+ diaryId = semester.diaryId,
+ kindergartenDiaryId = semester.kindergartenDiaryId,
+ schoolYear = semester.schoolYear,
+ unitId = semester.unitId,
+ )
+}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
new file mode 100644
index 000000000..3d41c711c
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
@@ -0,0 +1,70 @@
+package io.github.wulkanowy.utils
+
+import android.util.AndroidRuntimeException
+import java.net.CookiePolicy
+import java.net.CookieStore
+import java.net.HttpCookie
+import java.net.URI
+import android.webkit.CookieManager as WebkitCookieManager
+import java.net.CookieManager as JavaCookieManager
+
+class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) {
+
+ private val webkitCookieManager: WebkitCookieManager? = getWebkitCookieManager()
+
+ /**
+ * @see [https://stackoverflow.com/a/70354583/6695449]
+ */
+ private fun getWebkitCookieManager(): WebkitCookieManager? {
+ return try {
+ WebkitCookieManager.getInstance()
+ } catch (e: AndroidRuntimeException) {
+ null
+ }
+ }
+
+ override fun put(uri: URI?, responseHeaders: Map>?) {
+ if (uri == null || responseHeaders == null) return
+ val url = uri.toString()
+ for (headerKey in responseHeaders.keys) {
+ if (headerKey == null || !(
+ headerKey.equals("Set-Cookie2", ignoreCase = true) ||
+ headerKey.equals("Set-Cookie", ignoreCase = true)
+ )
+ ) continue
+
+ // process each of the headers
+ for (headerValue in responseHeaders[headerKey].orEmpty()) {
+ webkitCookieManager?.setCookie(url, headerValue)
+ }
+ }
+ }
+
+ override operator fun get(
+ uri: URI?,
+ requestHeaders: Map?>?
+ ): Map> {
+ require(!(uri == null || requestHeaders == null)) { "Argument is null" }
+ val res = mutableMapOf>()
+ val cookie = webkitCookieManager?.getCookie(uri.toString())
+ if (cookie != null) res["Cookie"] = listOf(cookie)
+ return res
+ }
+
+ override fun getCookieStore(): CookieStore {
+ val cookies = super.getCookieStore()
+ return object : CookieStore {
+ override fun add(uri: URI?, cookie: HttpCookie?) = cookies.add(uri, cookie)
+ override fun get(uri: URI?): List = cookies.get(uri)
+ override fun getCookies(): List = cookies.cookies
+ override fun getURIs(): List = cookies.urIs
+ override fun remove(uri: URI?, cookie: HttpCookie?): Boolean =
+ cookies.remove(uri, cookie)
+
+ override fun removeAll(): Boolean {
+ webkitCookieManager?.removeAllCookies(null) ?: return false
+ return true
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt
index c994ebab6..db16a2563 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt
@@ -16,6 +16,7 @@ import android.util.Base64.DEFAULT
import android.util.Base64.decode
import android.util.Base64.encode
import android.util.Base64.encodeToString
+import dagger.hilt.android.qualifiers.ApplicationContext
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@@ -33,108 +34,124 @@ import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.spec.OAEPParameterSpec
import javax.crypto.spec.PSource.PSpecified
+import javax.inject.Inject
+import javax.inject.Singleton
import javax.security.auth.x500.X500Principal
-private const val KEYSTORE_NAME = "AndroidKeyStore"
+@Singleton
+class Scrambler @Inject constructor(
+ @ApplicationContext private val context: Context,
+) {
+ private val keyCharset = Charset.forName("UTF-8")
-private const val KEY_ALIAS = "wulkanowy_password"
+ private val isKeyPairExists: Boolean
+ get() = keyStore.getKey(KEY_ALIAS, null) != null
-private val KEY_CHARSET = Charset.forName("UTF-8")
+ private val keyStore: KeyStore
+ get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) }
-private val isKeyPairExists: Boolean
- get() = keyStore.getKey(KEY_ALIAS, null) != null
+ private val cipher: Cipher
+ get() {
+ return if (SDK_INT >= M) Cipher.getInstance(
+ "RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
+ "AndroidKeyStoreBCWorkaround"
+ )
+ else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL")
+ }
-private val keyStore: KeyStore
- get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) }
+ fun encrypt(plainText: String): String {
+ if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
-private val cipher: Cipher
- get() {
- return if (SDK_INT >= M) Cipher.getInstance(
- "RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
- "AndroidKeyStoreBCWorkaround"
- )
- else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL")
+ return try {
+ if (!isKeyPairExists) generateKeyPair()
+
+ cipher.let {
+ if (SDK_INT >= M) {
+ OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec ->
+ it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey, spec)
+ }
+ } else it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey)
+
+ ByteArrayOutputStream().let { output ->
+ CipherOutputStream(output, it).apply {
+ write(plainText.toByteArray(keyCharset))
+ close()
+ }
+ encodeToString(output.toByteArray(), DEFAULT)
+ }
+ }
+ } catch (exception: Exception) {
+ Timber.e(exception, "An error occurred while encrypting text")
+ String(encode(plainText.toByteArray(keyCharset), DEFAULT), keyCharset)
+ }
}
-fun encrypt(plainText: String, context: Context): String {
- if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
+ fun decrypt(cipherText: String): String {
+ if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
- return try {
- if (!isKeyPairExists) generateKeyPair(context)
+ return try {
+ if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist")
- cipher.let {
- if (SDK_INT >= M) {
- OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec ->
- it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey, spec)
+ cipher.let {
+ if (SDK_INT >= M) {
+ OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec ->
+ it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null), spec)
+ }
+ } else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null))
+
+ CipherInputStream(
+ ByteArrayInputStream(decode(cipherText, DEFAULT)),
+ it
+ ).let { input ->
+ val values = ArrayList()
+ var nextByte: Int
+ while (run { nextByte = input.read(); nextByte } != -1) {
+ values.add(nextByte.toByte())
+ }
+ val bytes = ByteArray(values.size)
+ for (i in bytes.indices) {
+ bytes[i] = values[i]
+ }
+ String(bytes, 0, bytes.size, keyCharset)
}
- } else it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey)
+ }
+ } catch (e: Exception) {
+ throw ScramblerException("An error occurred while decrypting text", e)
+ }
+ }
- ByteArrayOutputStream().let { output ->
- CipherOutputStream(output, it).apply {
- write(plainText.toByteArray(KEY_CHARSET))
- close()
- }
- encodeToString(output.toByteArray(), DEFAULT)
+ private fun generateKeyPair() {
+ (if (SDK_INT >= M) {
+ KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
+ .setDigests(DIGEST_SHA256, DIGEST_SHA512)
+ .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP)
+ .setCertificateSerialNumber(BigInteger.TEN)
+ .setCertificateSubject(X500Principal("CN=Wulkanowy"))
+ .build()
+ } else {
+ KeyPairGeneratorSpec.Builder(context)
+ .setAlias(KEY_ALIAS)
+ .setSubject(X500Principal("CN=Wulkanowy"))
+ .setSerialNumber(BigInteger.TEN)
+ .setStartDate(Calendar.getInstance().time)
+ .setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time)
+ .build()
+ }).let {
+ KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply {
+ initialize(it)
+ genKeyPair()
}
}
- } catch (exception: Exception) {
- Timber.e(exception, "An error occurred while encrypting text")
- String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
+ Timber.i("A new KeyPair has been generated")
+ }
+
+ fun clearKeyPair() {
+ keyStore.deleteEntry(KEY_ALIAS)
+ Timber.i("KeyPair has been cleared")
+ }
+
+ private companion object {
+ private const val KEYSTORE_NAME = "AndroidKeyStore"
+ private const val KEY_ALIAS = "wulkanowy_password"
}
}
-
-fun decrypt(cipherText: String): String {
- if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
-
- return try {
- if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist")
-
- cipher.let {
- if (SDK_INT >= M) {
- OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec ->
- it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null), spec)
- }
- } else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null))
-
- CipherInputStream(ByteArrayInputStream(decode(cipherText, DEFAULT)), it).let { input ->
- val values = ArrayList()
- var nextByte: Int
- while (run { nextByte = input.read(); nextByte } != -1) {
- values.add(nextByte.toByte())
- }
- val bytes = ByteArray(values.size)
- for (i in bytes.indices) {
- bytes[i] = values[i]
- }
- String(bytes, 0, bytes.size, KEY_CHARSET)
- }
- }
- } catch (e: Exception) {
- throw ScramblerException("An error occurred while decrypting text", e)
- }
-}
-
-private fun generateKeyPair(context: Context) {
- (if (SDK_INT >= M) {
- KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
- .setDigests(DIGEST_SHA256, DIGEST_SHA512)
- .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP)
- .setCertificateSerialNumber(BigInteger.TEN)
- .setCertificateSubject(X500Principal("CN=Wulkanowy"))
- .build()
- } else {
- KeyPairGeneratorSpec.Builder(context)
- .setAlias(KEY_ALIAS)
- .setSubject(X500Principal("CN=Wulkanowy"))
- .setSerialNumber(BigInteger.TEN)
- .setStartDate(Calendar.getInstance().time)
- .setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time)
- .build()
- }).let {
- KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply {
- initialize(it)
- genKeyPair()
- }
- }
- Timber.i("A new KeyPair has been generated")
-}
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 907221d79..ef6308b6c 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,7 +1,7 @@
-Wersja 2.2.4
+Wersja 2.4.2
-— ułatwiliśmy przełączenie dnia na weekend w planie lekcji przy użyciu strzałek
-— poprawiliśmy wsparcie dla statystyk ocen z systemem punktowym
-— poprawiliśmy sortowanie nauczycieli w widoku Szkoła i nauczyciele
+- naprawiliśmy crash przy przełączaniu uczniów, motywów i języków
+- naprawiliśmy crash przy dodawaniu dodatkowych lekcji
+- naprawiliśmy obsługę błędów widżetach
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
diff --git a/app/src/main/res/drawable/ic_circle_notification.xml b/app/src/main/res/drawable/ic_circle_notification.xml
new file mode 100644
index 000000000..6059212cb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_circle_notification.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml
new file mode 100644
index 000000000..1d6c00461
--- /dev/null
+++ b/app/src/main/res/drawable/ic_close.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_menu_message_delete_forever.xml b/app/src/main/res/drawable/ic_menu_message_delete_forever.xml
new file mode 100644
index 000000000..a7b5ac53b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_message_delete_forever.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_menu_message_restore.xml b/app/src/main/res/drawable/ic_menu_message_restore.xml
new file mode 100644
index 000000000..5c8544f28
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_message_restore.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_notifications_off.xml b/app/src/main/res/drawable/ic_notifications_off.xml
new file mode 100644
index 000000000..094ed75fa
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notifications_off.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index d14de50a1..a9284234e 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -16,7 +16,7 @@
+ android:layout_height="wrap_content" />
@@ -67,7 +67,7 @@
android:layout_height="wrap_content"
android:editable="false"
android:focusable="false"
- android:inputType="text"
+ android:inputType="none"
tools:ignore="Deprecated" />
@@ -87,7 +87,7 @@
android:layout_height="wrap_content"
android:editable="false"
android:focusable="false"
- android:inputType="text"
+ android:inputType="none"
tools:ignore="Deprecated" />
@@ -113,7 +113,7 @@
android:id="@+id/additionalLessonDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="24dp"
android:layout_marginEnd="8dp"
@@ -122,6 +122,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/additionalLessonDialogAdd"
@@ -131,7 +132,7 @@
android:id="@+id/additionalLessonDialogAdd"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
@@ -139,6 +140,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_ads_consent.xml b/app/src/main/res/layout/dialog_ads_consent.xml
deleted file mode 100644
index 118fb9c1f..000000000
--- a/app/src/main/res/layout/dialog_ads_consent.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/dialog_attendance.xml b/app/src/main/res/layout/dialog_attendance.xml
index d832490a7..b9c63539c 100644
--- a/app/src/main/res/layout/dialog_attendance.xml
+++ b/app/src/main/res/layout/dialog_attendance.xml
@@ -150,7 +150,7 @@
android:id="@+id/attendanceDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -158,6 +158,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_captcha.xml b/app/src/main/res/layout/dialog_captcha.xml
new file mode 100644
index 000000000..539aa0cc9
--- /dev/null
+++ b/app/src/main/res/layout/dialog_captcha.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_conference.xml b/app/src/main/res/layout/dialog_conference.xml
index 72837b819..3d33fd568 100644
--- a/app/src/main/res/layout/dialog_conference.xml
+++ b/app/src/main/res/layout/dialog_conference.xml
@@ -181,7 +181,7 @@
android:id="@+id/conferenceDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -189,6 +189,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_exam.xml b/app/src/main/res/layout/dialog_exam.xml
index 519d1531f..e2f77e1f2 100644
--- a/app/src/main/res/layout/dialog_exam.xml
+++ b/app/src/main/res/layout/dialog_exam.xml
@@ -220,7 +220,7 @@
android:id="@+id/examDialogAddToCalendar"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="24dp"
android:contentDescription="@string/all_add_to_calendar"
@@ -228,6 +228,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_add"
app:icon="@drawable/ic_calendar_all"
app:layout_constraintBottom_toBottomOf="parent"
@@ -237,7 +238,7 @@
android:id="@+id/examDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -245,6 +246,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
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 f47f61088..8606a5ce5 100644
--- a/app/src/main/res/layout/dialog_grade.xml
+++ b/app/src/main/res/layout/dialog_grade.xml
@@ -212,7 +212,7 @@
android:id="@+id/gradeDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_below="@+id/gradeDialogColorValue"
android:layout_alignParentRight="true"
android:layout_marginTop="24dp"
@@ -222,6 +222,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close" />
diff --git a/app/src/main/res/layout/dialog_homework.xml b/app/src/main/res/layout/dialog_homework.xml
index 8c6cf0a76..10b719077 100644
--- a/app/src/main/res/layout/dialog_homework.xml
+++ b/app/src/main/res/layout/dialog_homework.xml
@@ -27,7 +27,7 @@
android:id="@+id/homeworkDialogRead"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginBottom="24dp"
@@ -35,6 +35,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/homework_mark_as_done"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/homeworkDialogClose" />
@@ -43,13 +44,14 @@
android:id="@+id/homeworkDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
diff --git a/app/src/main/res/layout/dialog_homework_add.xml b/app/src/main/res/layout/dialog_homework_add.xml
index e0ff5b749..dc7ae32d5 100644
--- a/app/src/main/res/layout/dialog_homework_add.xml
+++ b/app/src/main/res/layout/dialog_homework_add.xml
@@ -94,7 +94,7 @@
android:id="@+id/homeworkDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="24dp"
android:layout_marginEnd="8dp"
@@ -103,6 +103,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/homeworkDialogAdd"
@@ -112,13 +113,14 @@
android:id="@+id/homeworkDialogAdd"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_lesson_completed.xml b/app/src/main/res/layout/dialog_lesson_completed.xml
index 3a1d3fd00..fc32a252a 100644
--- a/app/src/main/res/layout/dialog_lesson_completed.xml
+++ b/app/src/main/res/layout/dialog_lesson_completed.xml
@@ -212,7 +212,7 @@
android:id="@+id/completedLessonDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -220,6 +220,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_mobile_device.xml b/app/src/main/res/layout/dialog_mobile_device.xml
index 9b81737fb..c526ed74c 100644
--- a/app/src/main/res/layout/dialog_mobile_device.xml
+++ b/app/src/main/res/layout/dialog_mobile_device.xml
@@ -18,10 +18,10 @@
android:layout_marginTop="24dp"
android:adjustViewBounds="true"
android:contentDescription="@string/mobile_device_qr"
- tools:src="@tools:sample/avatars"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@tools:sample/avatars" />
+
+ app:constraint_referenced_ids="mobileDeviceQr,mobileDeviceDialogTokenTitle,mobileDeviceDialogTokenValue,mobileDeviceDialogSymbolTitle,mobileDeviceDialogSymbolValue,mobileDeviceDialogPinTitle,mobileDeviceDialogPinValue,mobileDeviceDialogClose"
+ tools:visibility="visible" />
+ tools:visibility="invisible" />
diff --git a/app/src/main/res/layout/dialog_note.xml b/app/src/main/res/layout/dialog_note.xml
index 9c8b18b32..3b88ea5f8 100644
--- a/app/src/main/res/layout/dialog_note.xml
+++ b/app/src/main/res/layout/dialog_note.xml
@@ -180,7 +180,7 @@
android:id="@+id/noteDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -188,6 +188,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_school_announcement.xml b/app/src/main/res/layout/dialog_school_announcement.xml
index 4e0ef556f..a771b772f 100644
--- a/app/src/main/res/layout/dialog_school_announcement.xml
+++ b/app/src/main/res/layout/dialog_school_announcement.xml
@@ -122,7 +122,7 @@
android:id="@+id/announcementDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -130,6 +130,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/dialog_timetable.xml b/app/src/main/res/layout/dialog_timetable.xml
index aeb01b3ba..de2696482 100644
--- a/app/src/main/res/layout/dialog_timetable.xml
+++ b/app/src/main/res/layout/dialog_timetable.xml
@@ -263,7 +263,7 @@
android:id="@+id/timetableDialogClose"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="24dp"
@@ -271,6 +271,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
+ android:minHeight="36dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
index 348602d77..9de44a70d 100644
--- a/app/src/main/res/layout/fragment_dashboard.xml
+++ b/app/src/main/res/layout/fragment_dashboard.xml
@@ -18,7 +18,8 @@
+ android:layout_height="match_parent"
+ tools:visibility="gone">
-
+
+
@@ -55,14 +70,21 @@
android:gravity="center"
android:padding="8dp"
android:text="@string/error_unknown"
- android:textSize="20sp" />
+ android:textSize="20sp"
+ app:layout_constraintBottom_toTopOf="@id/dashboard_error_buttons"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/dashboard_error_image" />
+ android:orientation="horizontal"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/dashboard_error_message">
-
+
diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml
index fc5e5f35e..10864e640 100644
--- a/app/src/main/res/layout/fragment_login_form.xml
+++ b/app/src/main/res/layout/fragment_login_form.xml
@@ -261,6 +261,7 @@
android:layout_marginEnd="24dp"
android:layout_marginRight="24dp"
android:hint="@string/login_domain_suffix_hint"
+ app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout"
diff --git a/app/src/main/res/layout/header_grade_details.xml b/app/src/main/res/layout/header_grade_details.xml
index f2ba9a8c9..e43e8993f 100644
--- a/app/src/main/res/layout/header_grade_details.xml
+++ b/app/src/main/res/layout/header_grade_details.xml
@@ -45,6 +45,9 @@
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/gradeHeaderPointsSum"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="@id/gradeHeaderSubject"
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
tools:text="Average: 6,00" />
@@ -55,8 +58,12 @@
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
+ android:ellipsize="end"
+ android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintEnd_toStartOf="@id/gradeHeaderNumber"
app:layout_constraintStart_toEndOf="@+id/gradeHeaderAverage"
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
tools:text="Points: 123/200 (61,5%)" />
@@ -67,8 +74,13 @@
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
+ android:layout_marginEnd="8dp"
+ android:ellipsize="end"
+ android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/gradeHeaderPointsSum"
app:layout_constraintTop_toBottomOf="@id/gradeHeaderSubject"
tools:text="12 grades" />
@@ -85,6 +97,9 @@
android:paddingRight="5dp"
android:textColor="?colorOnPrimary"
android:textSize="14sp"
+ app:autoSizeMaxTextSize="16dp"
+ app:autoSizeMinTextSize="10dp"
+ app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
diff --git a/app/src/main/res/layout/item_dashboard_admin_message.xml b/app/src/main/res/layout/item_dashboard_admin_message.xml
index e12241df5..407e12921 100644
--- a/app/src/main/res/layout/item_dashboard_admin_message.xml
+++ b/app/src/main/res/layout/item_dashboard_admin_message.xml
@@ -34,11 +34,24 @@
android:layout_marginEnd="16dp"
android:textSize="18sp"
android:textStyle="bold"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/dashboard_admin_message_item_close"
app:layout_constraintStart_toEndOf="@id/dashboard_admin_message_item_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
+
+
+ android:layout_height="1dp"
+ android:id="@+id/gradeSummaryItemPointsDivider"
+ android:background="@drawable/ic_all_divider" />
+ android:id="@+id/gradeSummaryItemPredictedDivider"
+ android:layout_height="1dp"
+ android:background="@drawable/ic_all_divider" />
+ android:layout_height="1dp"
+ android:id="@+id/gradeSummaryItemFinalDivider"
+ android:background="@drawable/ic_all_divider" />
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml
index 39fbaad01..1346c3f05 100644
--- a/app/src/main/res/layout/item_message.xml
+++ b/app/src/main/res/layout/item_message.xml
@@ -81,9 +81,9 @@
@@ -83,13 +94,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
- android:layout_marginTop="0dp"
- android:layout_marginEnd="5dp"
+ android:layout_marginEnd="0dp"
+ android:ellipsize="end"
+ android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@+id/timetableItemTeacher"
app:layout_constraintStart_toEndOf="@+id/timetableItemRoom"
- app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish"
+ app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish"
tools:text="(2/2)"
tools:visibility="visible" />
@@ -98,13 +110,15 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
- android:layout_marginEnd="16dp"
+ android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
- app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/timetableItemGroup"
+ app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish"
tools:text="Agata Kowalska - Błaszczyk"
tools:visibility="visible" />
@@ -118,8 +132,8 @@
android:textColor="?colorTimetableChange"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@+id/timetableItemTeacher"
- app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish"
+ app:layout_constraintStart_toEndOf="@id/timetableItemTimeFinish"
+ app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish"
tools:text="Lekcja odwołana: uczniowie zwolnieni do domu"
tools:visibility="gone" />
@@ -168,7 +182,7 @@
android:visibility="gone"
app:backgroundTint="?colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/timetableItemTimeStart"
tools:text="jeszcze 15 min"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout/item_widget_timetable_error.xml b/app/src/main/res/layout/item_widget_timetable_error.xml
new file mode 100644
index 000000000..6f9ab067a
--- /dev/null
+++ b/app/src/main/res/layout/item_widget_timetable_error.xml
@@ -0,0 +1,12 @@
+
+
diff --git a/app/src/main/res/layout/subitem_dashboard_small_grade.xml b/app/src/main/res/layout/subitem_dashboard_small_grade.xml
index 6800b72e9..5d48313a3 100644
--- a/app/src/main/res/layout/subitem_dashboard_small_grade.xml
+++ b/app/src/main/res/layout/subitem_dashboard_small_grade.xml
@@ -1,5 +1,6 @@
diff --git a/app/src/main/res/layout/widget_timetable.xml b/app/src/main/res/layout/widget_timetable.xml
index b07cc78f6..b438da6c3 100644
--- a/app/src/main/res/layout/widget_timetable.xml
+++ b/app/src/main/res/layout/widget_timetable.xml
@@ -114,7 +114,5 @@
android:text="@string/widget_timetable_no_items"
android:textAppearance="?attr/textAppearanceBody1"
android:visibility="gone" />
-
-
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 57cf05ddb..04af86713 100644
--- a/app/src/main/res/menu/action_menu_message_preview.xml
+++ b/app/src/main/res/menu/action_menu_message_preview.xml
@@ -29,6 +29,13 @@
android:title="@string/message_forward"
app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" />
+
+
+
diff --git a/app/src/main/res/menu/context_menu_attendance.xml b/app/src/main/res/menu/context_menu_attendance.xml
index 4eff14e3f..45377a76f 100644
--- a/app/src/main/res/menu/context_menu_attendance.xml
+++ b/app/src/main/res/menu/context_menu_attendance.xml
@@ -2,6 +2,13 @@
Ladění
Ladění oznámení
+ Vymazat soubory cookie webview
Tvůrci
Licence
Zprávy
@@ -55,6 +56,7 @@
Neplatný e-mail
Místo e-mailu použijte přiřazené přihlašovací údaje
Použijte přiřazené přihlašovací nebo e-mail v @%1$s
+ Invalid domain suffix
Neplatný symbol. Pokud jej nemůžete najít, kontaktujte školu
Nevymýšlejte si! Pokud symbol nemůžete najít, kontaktujte školu
Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+
@@ -96,6 +98,8 @@
Přihlásit se
Relace vypršela
Relace vypršela. Přihlaste se prosím znovu
+ Heslo k vašemu účtu bylo změněno. Musíte se znovu přihlásit do Wulkanového
+ Heslo bylo změněno
Podpora aplikace
Líbí se Vám tato aplikace? Podpořte její vývoj tím, že povolíte neinvazivní reklamy, které můžete kdykoliv vypnout
Zapnout reklamy
@@ -114,6 +118,7 @@
Součet bodů
Konečná známka
Předpokládaná známka
+ Popisná známka
Vypočítaný průměr
Jak funguje vypočítaný průměr?
Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\nPrůměr známek pouze z vybraného semestru:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\nPrůměr průměrů z obou semestrů:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru sečtených průměrů\n\nPrůměr známek z celého roku:\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů
@@ -157,6 +162,12 @@
- Nové konečné známky
- Nové konečné známky
+
+ - Nová popisná známka
+ - Nové popisné známky
+ - Nové popisné známky
+ - Nové popisné známky
+
- Máte %1$d novou známku
- Máte %1$d nové známky
@@ -175,6 +186,12 @@
- Máte %1$d nových konečných známek
- Máte %1$d nových konečných známek
+
+ - Máte %1$d novou popisnou známku
+ - Máte %1$d nové popisné známky
+ - Máte %1$d nových popisných známek
+ - Máte %1$d nových popisných známek
+
Lekce
Učebna
@@ -760,7 +777,7 @@
Podpora
Ochrana osobních údajů
Souhlasy
- Souhlas se zpracováním údajů souvisejících s reklamami
+ Zobrazit souhlas se zpracováním údajů
Zobrazit reklamy v aplikaci
Podívejte se na jednu reklamu pro podporu projektu
Souhlas se zpracováním dat
@@ -769,13 +786,6 @@
Ochrana osobních údajů
Reklama se načítá
Děkujeme za vaši podporu, vraťte se později pro více reklam
- Můžeme použít Vaše data k zobrazení reklam?
- 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í
@@ -838,17 +848,27 @@
Autorizace
Pro provoz aplikace potřebujeme potvrdit vaši identitu. Zadejte PESEL žáka <b>%1$s</b> v níže uvedeném poli
Zatím přeskočit
+
+ Probíhá ověřování. Počkejte…
+ Úspěšně ověřeno
Žádné internetové připojení
Vyskytla se chyba. Zkontrolujte hodiny svého zařízení
+ This account is inactive. Try logging in again
Nelze se připojit ke deníku. Servery mohou být přetíženy. Prosím zkuste to znovu později
Načítání dat se nezdařilo. Prosím zkuste to znovu později
Je vyžadována změna hesla pro deník
Probíhá údržba deníku UONET+. Zkuste to později znovu
Neznámá chyba deniku UONET+. Prosím zkuste to znovu později
Neznámá chyba aplikace. Prosím zkuste to znovu později
+ Vyžadováno ověření Captcha
Vyskytla se neočekávaná chyba
Funkce je deaktivována přes vaší školou
Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API
Toto pole je povinné
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml
deleted file mode 100644
index a89b83769..000000000
--- a/app/src/main/res/values-da-rDK/strings.xml
+++ /dev/null
@@ -1,764 +0,0 @@
-
-
-
- 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
- Menu configuartion
-
- 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
- Custom domain suffix
- Mobile API
- Scraper
- Hybrid
- Token
- PIN
- Symbol
- E.g. \"lodz\" or \"powiatjaroslawski\"
- 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. If you cannot find it, please contact the school
- Don\'t make this up! If you cannot find it, please contact the school
- 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!
- Reset password
- Recover your account
- Recover
- Student is already signed in
- Standard
- Other search locations
- No active students found
- Enter a different symbol
- Get help
- Full school name with the town (required)
- Np. ZSTiO Jarosław lub SP nr 99 w Łodzi
- Enter correct name of the school
- Additional information in Polish (optional)
- Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\"
- Submit
-
- 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
-
- - No lesson
- - No lessons
-
-
- - 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
- Incognito mode is on
- Thanks to incognito mode sender is not notified when you read the message
-
- 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
- Cancel
-
- No lessons
- Synchronized on %1$s at %2$s
- Choose theme
- Light
- Dark
- System Theme
-
- App
- Default view
- Calculated average options
- Force average calculation by app
- Show presence
- Theme
- Grades expanding
- Show groups next to subjects
- Show empty tiles where there\'s no lesson
- Show chart list in class grades
- Show subjects without grades
- Grades color scheme
- Subjects sorting
- Language
- Menu configuration
- Set the order of functions in the menu
- 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
- Incognito mode
- Do not inform about reading the message
- 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
-
- Application restart
- The application must restart for the changes to be saved
- Restart
-
- Authorization has been rejected. The data provided does not match the records in the secretary\'s office.
- Invalid PESEL
- PESEL
- Authorize
- Authorization completed successfully
- Authorization
- To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
- Skip for now
-
- 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 4c9aa2d28..a346bbd2f 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -10,9 +10,10 @@
Einstellungen
Mehr
Über die Applikation
- Log Viewer
+ Log viewer
Debuggen
Benachrichtigungen debuggen
+ Clear webview cookies
Mitarbeiter
Lizenzen
Nachrichten
@@ -55,6 +56,7 @@
Ungültige email
Den zugewiesenen Login anstelle von email verwenden
Benutze den zugewiesenen Login oder E-Mail in @%1$s
+ Invalid domain suffix
Invalid symbol. If you cannot find it, please contact the school
Don\'t make this up! If you cannot find it, please contact the school
Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers
@@ -96,6 +98,8 @@
Anmelden
Die Sitzung ist abgelaufen
Die Sitzung ist abgelaufen, bitte loggen Sie sich erneut ein
+ Your account password has been changed. You need to log in to Wulkanowy again
+ Password changed
Anwendungsunterstützung
Gefällt Ihnen diese App? Unterstützen Sie ihre Entwicklung, indem Sie nicht-invasive Werbung aktivieren, die Sie jederzeit deaktivieren können
Werbung aktivieren
@@ -114,6 +118,7 @@
Gesamtpunkte
Finaler Note
Vorhergesagte Note
+ Descriptive grade
Berechnender Durchschnitt
Wie funktioniert der berechnete Durchschnitt?
Der berechnete Mittelwert ist das arithmetische Mittel, das aus den Durchschnittswerten der Probanden errechnet wird. Es erlaubt Ihnen, den ungefähre endgültigen Durchschnitt zu kennen. Sie wird auf eine vom Anwender in den Anwendungseinstellungen gewählte Weise berechnet. Es wird empfohlen, die entsprechende Option zu wählen. Das liegt daran, dass die Berechnung der Schuldurchschnitte unterschiedlich ist. Wenn Ihre Schule den Durchschnitt der Fächer auf der Vulcan-Seite angibt, lädt die Anwendung diese Fächer herunter und berechnet nicht den Durchschnitt. Dies kann geändert werden, indem die Berechnung des Durchschnitts in den Anwendungseinstellungen erzwungen wird. \n\nDurchschnitt der Noten nur aus dem ausgewählten Semester :\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in einem bestimmten Semester\n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Durchschnitte aus beiden Semestern:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in Semester 1 und 2\n2. Berechnung des arithmetischen Mittels der berechneten Durchschnitte für Semester 1 und 2 für jedes Fach. \n3. Hinzufügen von berechneten Durchschnittswerten\n4. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Noten aus dem ganzen Jahr:\n1. Berechnung des gewichteten Jahresdurchschnitts für jedes Fach. Der Abschlussdurchschnitt im 1. Semester ist irrelevant. \n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Mittelwerte
@@ -149,6 +154,10 @@
- Neue Abschlussnote
- Neue Abschlussnoten
+
+ - New descriptive grade
+ - New descriptive grades
+
- Du hast %1$d Note bekommen
- Du hast %1$d Noten bekommen
@@ -161,6 +170,10 @@
- Sie haben %1$d Abschlussnote bekommen
- Sie haben %1$d Abschlussnoten bekommen
+
+ - You received %1$d descriptive grade
+ - You received %1$d descriptive grades
+
Lektion
Klassenzimmer
@@ -670,7 +683,7 @@
Unterstützung
Datenschutz-Bestimmungen
Vereinbarungen
- Zustimmung zur Verarbeitung von Daten im Zusammenhang mit Anzeigen
+ Show consent to data processing
Anzeigen in der App anzeigen
Einzelanzeige ansehen, um Projekt zu unterstützen
Einwilligung in die Datenverarbeitung
@@ -679,13 +692,6 @@
Datenschutzerklärung
Anzeige wird geladen
Vielen Dank für Ihre Unterstützung, kommen Sie später wieder für weitere Anzeigen
- Können wir Ihre Daten zur Anzeige von Werbung verwenden?
- 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
@@ -748,17 +754,27 @@
Authorization
To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
Skip for now
+
+ Verification is in progress. Wait…
+ Verified successfully
Keine Internetverbindung
Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr
+ This account is inactive. Try logging in again
Registrierungsverbindung fehlgeschlagen. Server können überlastet sein. Bitte versuchen Sie es später noch einmal
Das Laden der Daten ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal
Passwortänderung für Registrierung erforderlich
Wartung im Gange UONET + Klassenbuch. Versuchen Sie es später noch einmal
Unbekannter UONET + Registerfehler. Versuchen Sie es später erneut
Unbekannter Anwendungsfehler. Bitte versuchen Sie es später noch einmal
+ Captcha verification required
Ein unerwarteter Fehler ist aufgetreten
Funktion, die von Ihrer Schule deaktiviert wurde
Feature in diesem Modus nicht verfügbar
Dieses Feld ist erforderlich
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
deleted file mode 100644
index a89b83769..000000000
--- a/app/src/main/res/values-es-rES/strings.xml
+++ /dev/null
@@ -1,764 +0,0 @@
-
-
-
- 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
- Menu configuartion
-
- 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
- Custom domain suffix
- Mobile API
- Scraper
- Hybrid
- Token
- PIN
- Symbol
- E.g. \"lodz\" or \"powiatjaroslawski\"
- 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. If you cannot find it, please contact the school
- Don\'t make this up! If you cannot find it, please contact the school
- 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!
- Reset password
- Recover your account
- Recover
- Student is already signed in
- Standard
- Other search locations
- No active students found
- Enter a different symbol
- Get help
- Full school name with the town (required)
- Np. ZSTiO Jarosław lub SP nr 99 w Łodzi
- Enter correct name of the school
- Additional information in Polish (optional)
- Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\"
- Submit
-
- 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
-
- - No lesson
- - No lessons
-
-
- - 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
- Incognito mode is on
- Thanks to incognito mode sender is not notified when you read the message
-
- 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
- Cancel
-
- No lessons
- Synchronized on %1$s at %2$s
- Choose theme
- Light
- Dark
- System Theme
-
- App
- Default view
- Calculated average options
- Force average calculation by app
- Show presence
- Theme
- Grades expanding
- Show groups next to subjects
- Show empty tiles where there\'s no lesson
- Show chart list in class grades
- Show subjects without grades
- Grades color scheme
- Subjects sorting
- Language
- Menu configuration
- Set the order of functions in the menu
- 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
- Incognito mode
- Do not inform about reading the message
- 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
-
- Application restart
- The application must restart for the changes to be saved
- Restart
-
- Authorization has been rejected. The data provided does not match the records in the secretary\'s office.
- Invalid PESEL
- PESEL
- Authorize
- Authorization completed successfully
- Authorization
- To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
- Skip for now
-
- 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-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml
deleted file mode 100644
index a89b83769..000000000
--- a/app/src/main/res/values-it-rIT/strings.xml
+++ /dev/null
@@ -1,764 +0,0 @@
-
-
-
- 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
- Menu configuartion
-
- 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
- Custom domain suffix
- Mobile API
- Scraper
- Hybrid
- Token
- PIN
- Symbol
- E.g. \"lodz\" or \"powiatjaroslawski\"
- 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. If you cannot find it, please contact the school
- Don\'t make this up! If you cannot find it, please contact the school
- 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!
- Reset password
- Recover your account
- Recover
- Student is already signed in
- Standard
- Other search locations
- No active students found
- Enter a different symbol
- Get help
- Full school name with the town (required)
- Np. ZSTiO Jarosław lub SP nr 99 w Łodzi
- Enter correct name of the school
- Additional information in Polish (optional)
- Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\"
- Submit
-
- 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
-
- - No lesson
- - No lessons
-
-
- - 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
- Incognito mode is on
- Thanks to incognito mode sender is not notified when you read the message
-
- 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
- Cancel
-
- No lessons
- Synchronized on %1$s at %2$s
- Choose theme
- Light
- Dark
- System Theme
-
- App
- Default view
- Calculated average options
- Force average calculation by app
- Show presence
- Theme
- Grades expanding
- Show groups next to subjects
- Show empty tiles where there\'s no lesson
- Show chart list in class grades
- Show subjects without grades
- Grades color scheme
- Subjects sorting
- Language
- Menu configuration
- Set the order of functions in the menu
- 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
- Incognito mode
- Do not inform about reading the message
- 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
-
- Application restart
- The application must restart for the changes to be saved
- Restart
-
- Authorization has been rejected. The data provided does not match the records in the secretary\'s office.
- Invalid PESEL
- PESEL
- Authorize
- Authorization completed successfully
- Authorization
- To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below
- Skip for now
-
- 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-night-v31/styles.xml b/app/src/main/res/values-night-v31/styles.xml
index 6e6c4d79c..808bc714d 100644
--- a/app/src/main/res/values-night-v31/styles.xml
+++ b/app/src/main/res/values-night-v31/styles.xml
@@ -32,6 +32,9 @@
- @color/material_dynamic_primary40
- @color/timetable_canceled_dark
- @color/timetable_change_dark
+ - @color/attendance_absence_dark
+ - @color/attendance_lateness_dark
+
- @color/colorErrorLight
- @color/colorDividerInverse
- @color/material_dynamic_secondary20
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
index 5d9aa22a6..5840a051e 100644
--- a/app/src/main/res/values-night/styles.xml
+++ b/app/src/main/res/values-night/styles.xml
@@ -21,6 +21,8 @@
- @color/colorSurfaceDark
- @color/timetable_canceled_dark
- @color/timetable_change_dark
+ - @color/attendance_absence_dark
+ - @color/attendance_lateness_dark
- @color/colorErrorLight
- @color/colorDividerInverse
- @color/colorSwipeRefreshDark
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 31b9ce32c..56a85ea2a 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -13,6 +13,7 @@
Przeglądarka logów
Debugowanie
Debugowanie powiadomień
+ Wyczyść ciasteczka webview
Twórcy
Licencje
Wiadomości
@@ -55,6 +56,7 @@
Nieprawidłowy adres e-mail
Użyj loginu zamiast adresu e-mail
Użyj loginu lub adresu e-mail w @%1$s
+ Nieprawidłowy sufiks domeny
Nieprawidłowy symbol. Jeśli nie możesz go znaleźć, skontaktuj się ze szkołą
Nie zmyślaj! Jeśli nie możesz znaleźć symbolu, skontaktuj się ze szkołą
Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+
@@ -96,6 +98,8 @@
Zaloguj się
Sesja wygasła
Sesja wygasła, zaloguj się ponownie
+ Hasło do Twojego konta zostało zmienione. Musisz zalogować się ponownie do Wulkanowego
+ Hasło zostało zmienione
Wparcie aplikacji
Podoba Ci się ta aplikacja? Wspieraj jej rozwój poprzez włączenie nieinwazyjnych reklam, które możesz wyłączyć w dowolnym momencie
Włącz reklamy
@@ -114,6 +118,7 @@
Suma punktów
Ocena końcowa
Przewidywana ocena
+ Ocena opisowa
Obliczona średnia
Jak działa obliczona średnia?
Obliczona średnia jest średnią arytmetyczną obliczoną ze średnich przedmiotów. Pozwala ona na poznanie przybliżonej średniej końcowej. Jest obliczana w sposób wybrany przez użytkownika w ustawieniach aplikacji. Zaleca się wybranie odpowiedniej opcji. Dzieje się tak dlatego, że obliczanie średnich w szkołach różni się. Dodatkowo, jeśli twoja szkoła ma włączone średnie przedmiotów na stronie dziennika Vulcan, aplikacja pobiera je i ich nie oblicza. Można to zmienić, wymuszając obliczanie średniej w ustawieniach aplikacji.\n\nŚrednia ocen tylko z wybranego semestru:\n1. Obliczanie średniej arytmetycznej każdego przedmiotu w danym semestrze\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej zsumowanych średnich\n\nŚrednia ze średnich z obu semestrów:\n1.Obliczanie średniej arytmetycznej każdego przedmiotu w semestrze 1 i 2\n2. Obliczanie średniej arytmetycznej obliczonych średnich w semestrze 1 i 2 każdego przedmiotu.\n3. Zsumowanie obliczonych średnich\n4. Obliczanie średniej arytmetycznej zsumowanych średnich\n\nŚrednia wszystkich ocen z całego roku:\n1. Obliczanie średniej arytmetycznej z każdego przedmiotu w ciągu całego roku. Końcowa ocena w 1 semestrze jest bez znaczenia.\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej z zsumowanych średnich
@@ -157,6 +162,12 @@
- Nowe oceny końcowe
- Nowe oceny końcowe
+
+ - Nowa ocena opisowa
+ - Nowe oceny opisowe
+ - Nowe oceny opisowe
+ - Nowe oceny opisowe
+
- Masz %1$d nową ocenę
- Masz %1$d nowe oceny
@@ -175,6 +186,12 @@
- Masz %1$d nowych końcowych ocen
- Masz %1$d nowych końcowych ocen
+
+ - Masz %1$d nową ocenę opisową
+ - Masz %1$d nowe oceny opisowe
+ - Masz %1$d nowych ocen opisowych
+ - Masz %1$d nowych ocen opisowych
+
Lekcja
Sala
@@ -760,7 +777,7 @@
Wsparcie
Polityka prywatności
Zgody
- Zgoda na przetwarzanie danych związanych z reklamami
+ Pokaż zgodę na przetwarzanie danych
Pokazuj reklamy w aplikacji
Obejrzyj pojedynczą reklamę, aby wesprzeć projekt
Zgoda na przetwarzanie danych
@@ -769,13 +786,6 @@
Polityka prywatności
Ładowanie reklamy
Dziękujemy za wsparcie, wróć później po więcej reklam
- Czy możemy używać Twoich danych do wyświetlania reklam?
- 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
@@ -838,17 +848,27 @@
Autoryzacja
Rodzicu, musimy mieć pewność, że Twój adres e-mail został powiązany z prawidłowym kontem ucznia. W celu autoryzacji konta podaj numer PESEL ucznia <b>%1$s</b> w polu poniżej
Na razie pomiń
+
+ Trwa weryfikacja. Czekaj…
+ Pomyślnie zweryfikowano
Brak połączenia z internetem
Wystąpił błąd. Sprawdź poprawność daty w urządzeniu
+ Konto jest nieaktywne. Spróbuj zalogować się ponownie
Nie udało się połączyć z dziennikiem. Serwery mogą być przeciążone. Spróbuj ponownie później
Ładowanie danych nie powiodło się. Spróbuj ponownie później
Wymagana zmiana hasła do dziennika
Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później
Nieznany błąd dziennika UONET+. Spróbuj ponownie później
Nieznany błąd aplikacji. Spróbuj ponownie później
+ Wymagana weryfikacja captcha
Wystąpił nieoczekiwany błąd
Funkcja wyłączona przez szkołę
Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API
To pole jest wymagane
+
+ Wycisz
+ Wyłącz wyciszenie
+ Wyciszyleś tego użytkownika
+ Wyłączyłeś wyciszenie tego użytkownika
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 89a94a59d..f7469675e 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -13,6 +13,7 @@
Просмотр журнала
Отладка
Отладка уведомлений
+ Clear webview cookies
Разработчики
Лицензии
Сообщения
@@ -55,6 +56,7 @@
Неверный e-mail
Используйте назначенный логин вместо e-mail
Используйте назначенный логин или email в @%1$s
+ Invalid domain suffix
Invalid symbol. If you cannot find it, please contact the school
Don\'t make this up! If you cannot find it, please contact the school
Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+
@@ -96,6 +98,8 @@
Войти
Сеанс истёк
Сеанс истёк, авторизуйтесь снова
+ Your account password has been changed. You need to log in to Wulkanowy again
+ Password changed
Поддержка приложения
Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время
Включить рекламу
@@ -114,6 +118,7 @@
Сумма баллов
Итоговая оценка
Ожидаемая оценка
+ Descriptive grade
Рассчитанная средняя оценка
Как работает \"Рассчитанная средняя оценка\"?
Рассчитанная средняя оценка - это среднее арифметическое, рассчитанное на основе средних оценок по предметам. Это позволяет узнать приблизительную итоговую среднюю оценку. Она рассчитывается способом, выбранным пользователем в настройках приложения. Рекомендуется выбрать подходящий вариант, так как каждая школа по разному считает среднюю оценку. Кроме того, если ваша школа выставляет средние оценки по предметам на странице Vulcan, приложение просто загрузит их. Это можно изменить, заставив приложение считать среднюю оценку в настройках.\n\nСредняя из оценок выбранного семестра:\n1. Вычисление средневзвешенного значения по каждому предмету за семестр\n2.Суммирование вычисленных значений\n3. Вычисление среднего арифметического суммированных значений\n\nСредняя из средних оценок семестров:\n1.Расчет средневзвешенного значения для каждого предмета в семестрах. \n2. Вычисление среднего арифметического из средневзвешенных значений для каждого предмета в семестрах.\n3. Суммирование средних арифметических\n4. Вычисление среднего арифматического из суммированных значений\n\nСредняя из оценок со всего года:\n1. Расчет средневзвешенного значения по каждому предмету за год. Итоговое среднее значение за 1 семестр не имеет значения.\n2. Суммирование вычисленных средних\n3. Расчет среднего арифметического суммированных чисел
@@ -157,6 +162,12 @@
- Новые итоговые оценки
- Новые итоговые оценки
+
+ - New descriptive grade
+ - New descriptive grades
+ - New descriptive grades
+ - New descriptive grades
+
- Вы получили %1$d новую оценку
- Вы получили %1$d новые оценки
@@ -175,6 +186,12 @@
- Вы получили %1$d новых итоговых оценок
- Вы получили %1$d новых итоговые оценки
+
+ - You received %1$d descriptive grade
+ - You received %1$d descriptive grades
+ - You received %1$d descriptive grades
+ - You received %1$d descriptive grades
+
Урок
Аудитория
@@ -760,7 +777,7 @@
Поддержка
Политика приватности
Соглашения
- Согласие на обработку данных, связанных с объявлениями
+ Show consent to data processing
Показать рекламу в приложении
Посмотреть рекламу для поддержки проекта
Согласие на обработку данных
@@ -769,13 +786,6 @@
Политика конфиденциальности
Реклама загружается
Спасибо за вашу поддержку, возвращайтесь позже для дополнительной рекламы
- Можем ли мы использовать ваши данные для показа рекламы?
- Вы можете изменить свой выбор в любое время в настройках приложения. Мы можем использовать ваши данные для показа объявлений в соответствии с вашими пожеланиями или, используя меньше данных, отображать неперсональную рекламу. Пожалуйста, ознакомьтесь с нашей политикой конфиденциальности для подробностей
- Персонализированная реклама
- Неперсонализированная реклама
- Я старше 18 лет
- Да, персонализировать рекламу
- Да, не персонализировать рекламу
Расширенные
Внешний вид и поведение
Уведомления
@@ -833,22 +843,32 @@
Авторизация отклонена. Предоставленные данные не соответствуют записям в кабинете секретаря.
Неправильный номер PESEL
Номер PESEL
- Authorize
+ Авторизовать
Авторизация прошла успешно
Авторизация
Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже
Пропустить сейчас
+
+ Verification is in progress. Wait…
+ Verified successfully
Интернет-соединение отсутствует
Произошла ошибка. Проверьте время на вашем устройстве
+ This account is inactive. Try logging in again
Не удалось подключиться к дневнику. Возможно, сервера перегружены, повторите попытку позже
Не удалось загрузить данные, повторите попытку позже
Необходимо изменить пароль дневника
UONET+ проводит техническое обслуживание, повторите попытку позже
Неизвестная ошибка дневника UONET+, повторите попытку позже
Неизвестная ошибка приложения, повторите попытку позже
+ Captcha verification required
Произошла непредвиденная ошибка
Функция отключена вашей школой
Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом
Это поле обязательно
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 3cde8152c..56238c10a 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -13,6 +13,7 @@
Prehliadač protokolov
Ladenie
Ladenie oznámení
+ Vymazať súbory cookie webview
Tvorcovia
Licencie
Správy
@@ -55,6 +56,7 @@
Neplatný e-mail
Namiesto e-mailu použite priradené prihlasovacie údaje
Použite priradené prihlasovacie alebo e-mail v @%1$s
+ Invalid domain suffix
Neplatný symbol. Pokiaľ ho nemôžete nájsť, kontaktujte školu
Nevymýšľajte si! Pokiaľ symbol nemôžete nájsť, kontaktujte školu
Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+
@@ -96,6 +98,8 @@
Prihlásiť sa
Relácia vypršala
Relácia vypršala. Prihláste sa prosím znovu
+ Heslo k vášmu účtu bolo zmenené. Musíte sa znovu prihlásiť do Wulkanového
+ Heslo bolo zmenené
Podpora aplikácie
Páči sa Vám táto aplikácia? Podporte jej vývoj tým, že povolíte neinvazívne reklamy, ktoré môžete kedykoľvek vypnúť
Zapnúť reklamy
@@ -114,6 +118,7 @@
Súčet bodov
Konečná známka
Predpokladaná známka
+ Popisná známka
Vypočítaný priemer
Ako funguje vypočítaný priemer?
Vypočítaný priemer je aritmetický priemer vypočítaný z priemerov predmetov. Umožňuje vám to poznať približný konečný priemer. Vypočítava sa spôsobom zvoleným užívateľom v nastaveniach aplikácii. Odporúča sa vybrať príslušnú možnosť. Dôvodom je rozdielny výpočet školských priemerov. Ak vaša škola navyše uvádza priemer predmetov na stránke denníka Vulcan, aplikácia si ich stiahne a tieto priemery nepočíta. To možno zmeniť vynútením výpočtu priemeru v nastavení aplikácii.\n\nPriemer známok iba z vybraného semestra:\n1. Výpočet váženého priemeru pre každý predmet v danom semestri\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer priemerov z oboch semestrov:\n1. Výpočet váženého priemeru pre každý predmet v semestri 1 a 2\n2. Výpočet aritmetického priemeru vypočítaných priemerov za semestre 1 a 2 pre každý predmet.\n3. Sčítanie vypočítaných priemerov\n4. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer známok z celého roka:\n1. Výpočet váženého priemeru za rok pre každý predmet. Konečný priemer v 1. semestri je nepodstatný.\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov
@@ -157,6 +162,12 @@
- Nové konečné známky
- Nové konečné známky
+
+ - Nová popisná známka
+ - Nové popisné známky
+ - Nové popisné známky
+ - Nové popisné známky
+
- Máte %1$d novú známku
- Máte %1$d nové známky
@@ -175,6 +186,12 @@
- Máte %1$d nových konečných známok
- Máte %1$d nových konečných známok
+
+ - Máte %1$d novú popisnú známku
+ - Máte %1$d nové popisné známky
+ - Máte %1$d nových popisných známok
+ - Máte %1$d nových popisných známok
+
Lekcia
Učebňa
@@ -760,7 +777,7 @@
Podpora
Ochrana osobných údajov
Súhlasy
- Súhlas so spracovaním údajov súvisiacich s reklamami
+ Zobraziť súhlas so spracovaním údajov
Zobraziť reklamy v aplikácii
Pozrite sa na jednu reklamu pre podporu projektu
Súhlas so spracovaním dát
@@ -769,13 +786,6 @@
Ochrana osobných údajov
Reklama sa načítava
Ďakujeme za vašu podporu, vráťte sa neskôr pre viac reklám
- Môžeme použiť Vaše údaje na zobrazenie reklám?
- 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
@@ -838,17 +848,27 @@
Autorizácia
Na prevádzku aplikácie potrebujeme potvrdiť vašu identitu. Zadajte PESEL žiaka <b>%1$s</b> v nižšie uvedenom poli
Zatiaľ preskočiť
+
+ Overovanie prebieha. Počkajte…
+ Úspešne overené
Žiadne internetové pripojenie
Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia
+ This account is inactive. Try logging in again
Nedá sa pripojiť ku denníku. Servery môžu byť preťažené. Prosím skúste to znova neskôr
Načítanie údajov zlyhalo. Skúste neskôr prosím
Je vyžadovaná zmena hesla pre denník
Prebieha údržba denníka UONET+. Skúste to neskôr znova
Neznáma chyba dennika UONET+. Prosím skúste to znova neskôr
Neznáma chyba aplikácie. Prosím skúste to znova neskôr
+ Vyžaduje sa overenie Captcha
Vyskytla sa neočakávaná chyba
Funkcia je deaktivovaná cez vašou školou
Funkcia nie je k dispozícii. Prihláste sa v inom režime než Mobile API
Toto pole je povinné
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 20a917aa4..a82027479 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -13,6 +13,7 @@
Переглядач логів
Відладка
Відладка сповіщень
+ Очистити кукі веб - перегляду
Розробники
Ліцензії
Листи
@@ -55,6 +56,7 @@
Недійсна адреса e-mail
Використовуйте призначений логін замість адреси e-mail
Використовуйте призначений логін або адресу e-mail в @%1$s
+ Invalid domain suffix
Некоректний символ. Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою
Не вигадуйте! Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою
Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+
@@ -96,6 +98,8 @@
Увійти
Минув термін дії сесії
Минув термін дії сесії, авторизуйтеся знову
+ Пароль вашого облікового запису був змінений. Ви повинні увійти в Wulkanowy знову
+ Пароль змінено
Підтримка додатку
Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час
Увімкнути рекламу
@@ -114,6 +118,7 @@
Всього балів
Підсумкова оцінка
Передбачувана оцінка
+ Описова оцінка
Розрахована середня оцінка
Як працює \"Розрахована середня оцінка\"?
Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів. Це дозволяє дізнатися приблизну кінцеву середню оцінку. Вона розраховується спосібом, обраним користувачем у налаштуваннях програми. Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку. Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки і не розраховує їх самостійно. Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\nСередні оцінки тільки за обраний семестр:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\nСереднє значення з обох семестрів:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\nСереднє значення оцінок за весь рік: \n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх
@@ -157,6 +162,12 @@
- Нові підсумкові оцінки
- Нові підсумкові оцінки
+
+ - Нова описова оцінка
+ - Нових описових оцінок
+ - Описових оцінок
+ - Нові описові оцінки
+
- Ви отримали %1$d нову оцінку
- Ви отримали %1$d нові оцінки
@@ -175,6 +186,12 @@
- Ви отримали %1$d нових підсумкових оцінок
- Ви отримали %1$d нових підсумкових оцінок
+
+ - Ви отримали %1$d описову оцінку
+ - Ви отримали %1$d нові описові оцінки
+ - Ви отримали %1$d нових описових оцінок
+ - Ви отримали %1$d нових описових оцінок
+
Урок
Аудиторія
@@ -760,7 +777,7 @@
Підтримка
Політика конфіденційності
Угоди
- Згода на обробку даних, пов\'язаних з рекламою
+ Показати згоду на обробку даних
Показувати рекламу в додатку
Подивіться одну рекламу для підтримки проєкту
Згода в обробці даних
@@ -769,13 +786,6 @@
Політика конфіденційності
Реклама завантажується
Дякуємо за вашу підтримку, повертайтеся пізніше для більшої кількості реклам
- Чи можемо ми використовувати ваші дані для висвітлювання реклами?
- Ви можете змінити свій вибір в будь-який час в налаштуваннях додатку. Ми можемо використовувати ваші дані для висвітлювання реклами, адаптованої до вас або, використовуючи менше ваших даних, висвітлювати неперсоналізовану рекламу. Перегляньте нашу Політику конфіденційності для подробиць
- Персоналізована реклама
- Неперсоналізована реклама
- Мені більше 18 років
- Так, персоналізована реклама
- Так, неперсоналізована реклама
Додатково
Вигляд та поведінка
Сповіщення
@@ -838,17 +848,27 @@
Авторизувати
Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче
Поки що пропустити
+
+ Верифікація в процесі. Чекайте…
+ Верифікація завершена
Немає з\'єднання з інтернетом
Сталася помилка. Перевірте годинник пристрою
+ This account is inactive. Try logging in again
Помилка підключення до щоденнику. Сервери можуть бути перевантажені, спробуйте пізніше
Помилка завантаження даних, спробуйте пізніше
Необхідна зміна пароля щоденника
UONET+ проводить технічне осблуговування, спробуйте пізніше
Невідома помилка щоденника UONET+, спробуйте пізніше
Невідома помилка програми, спробуйте пізніше
+ Необхідна перевірка Captcha
Відбулася несподівана помилка
Функція вимкнена вашою школою
Функція недоступна в режимі Mobile API. Увійдіть в інший режим
Це поле обовʼязкове
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values-v31/styles.xml b/app/src/main/res/values-v31/styles.xml
index bb47b22ed..358806681 100644
--- a/app/src/main/res/values-v31/styles.xml
+++ b/app/src/main/res/values-v31/styles.xml
@@ -34,6 +34,9 @@
- @color/material_dynamic_primary80
- @color/timetable_canceled_light
- @color/timetable_change_light
+ - @color/attendance_absence_light
+ - @color/attendance_lateness_light
+
- @color/colorError
- @color/colorDivider
- @color/material_dynamic_secondary90
diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml
index 522b6e116..9768329d0 100644
--- a/app/src/main/res/values/api_hosts.xml
+++ b/app/src/main/res/values/api_hosts.xml
@@ -44,10 +44,10 @@
- https://vulcan.net.pl/?login
- https://vulcan.net.pl/?login
- https://vulcan.net.pl/?email&customSuffix
- - https://fakelog.cf/?email
+ - https://wulkanowy.net.pl/?email
- - Default
+ - warszawa
- opole
- gdansk
- lublin
@@ -66,7 +66,7 @@
- gminaulanmajorat
- gminaozorkow
- gminalopiennikgorny
- - Default
+ - saas1
- powiatwulkanowy
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index aa58fa09e..3c3fdb8e9 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -7,4 +7,6 @@
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 87057c61d..8ad27ad88 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -49,6 +49,12 @@
#ff8f00
#ffd54f
+ #d32f2f
+ #e57373
+
+ #cd2a01
+ #f05d0e
+
#1f000000
#1fffffff
diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml
index 5afffb649..74af9262c 100644
--- a/app/src/main/res/values/preferences_keys.xml
+++ b/app/src/main/res/values/preferences_keys.xml
@@ -37,8 +37,7 @@
single_ad_support
ads_enabled
ads_privacy_policy
- ads_consent_data_processing
- ads_over_eighteen
+ ads_ump_agreements
incognito_mode
appearance_menu_order
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5cad09d0b..b509b2710 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -14,6 +14,7 @@
Log viewer
Debug
Notification debug
+ Clear webview cookies
Contributors
Licenses
Messages
@@ -60,6 +61,7 @@
Invalid email
Use the assigned login instead of email
Use the assigned login or email in @%1$s
+ Invalid domain suffix
Invalid symbol. If you cannot find it, please contact the school
Don\'t make this up! If you cannot find it, please contact the school
Student not found. Validate the symbol and the chosen variation of the UONET+ register
@@ -107,6 +109,8 @@
Log in
Session expired
Session expired, log in again
+ Your account password has been changed. You need to log in to Wulkanowy again
+ Password changed
Application support
Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time
Enable ads
@@ -127,6 +131,7 @@
Total points
Final grade
Predicted grade
+ Descriptive 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
@@ -162,6 +167,10 @@
- New final grade
- New final grades
+
+ - New descriptive grade
+ - New descriptive grades
+
- You received %1$d grade
- You received %1$d grades
@@ -174,6 +183,10 @@
- You received %1$d final grade
- You received %1$d final grades
+
+ - You received %1$d descriptive grade
+ - You received %1$d descriptive grades
+
@@ -261,8 +274,10 @@
Absence excuse request sent successfully!
You must select at least one absence!
Excuse
+ Excuse entire day
z powodu
- Dzień dobry,\nProszę o usprawiedliwienie mojego dziecka w dniu %s z lekcji %s%s%s.\n\nPozdrawiam.
+ Dzień dobry,\nProszę o usprawiedliwienie mojego dziecka w dniu %s z lekcji %s%s%s.\n\nPozdrawiam.
+ Dzień dobry,\nProszę o usprawiedliwienie mojego dziecka w dniu %s%s%s.\n\nPozdrawiam.
- New attendance
- New attendance
@@ -312,8 +327,10 @@
Forward
Select all
Unselect all
+ Restore from trash
Move to trash
Delete permanently
+ Message restored successfully
Message deleted successfully
student
parent
@@ -351,6 +368,7 @@
- %1$d selected
Messages deleted
+ Messages restored
Choose mailbox
Incognito mode is on
Thanks to incognito mode sender is not notified when you read the message
@@ -749,7 +767,7 @@
Support
Privacy Policy
Agreements
- Consent to processing of data related to ads
+ Show consent to data processing
Show ads in app
Watch single ad to support project
Consent to data processing
@@ -758,13 +776,6 @@
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
@@ -838,17 +849,30 @@
Skip for now
+
+ Verification is in progress. Wait…
+ Verified successfully
+
+
No internet connection
An error occurred. Check your device clock
+ This account is inactive. Try logging in again
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
+ Captcha verification required
An unexpected error occurred
Feature disabled by your school
Feature not available. Login in a mode other than Mobile API
This field is required
+
+
+ Mute
+ Unmute
+ You have muted this user
+ You have unmuted this user
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index a0023dda1..b5b029501 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -19,6 +19,8 @@
- @color/colorSurface
- @color/timetable_canceled_light
- @color/timetable_change_light
+ - @color/attendance_absence_light
+ - @color/attendance_lateness_light
- @color/colorError
- @color/colorDivider
- @color/colorSwipeRefresh
diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt
index af6a83404..30b9e6b77 100644
--- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt
+++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt
@@ -2,19 +2,17 @@ package io.github.wulkanowy.ui.modules.settings.ads
import android.os.Bundle
import android.view.View
-import androidx.preference.CheckBoxPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
-import io.github.wulkanowy.databinding.DialogAdsConsentBinding
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.openInternetBrowser
import javax.inject.Inject
@@ -24,6 +22,9 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
@Inject
lateinit var presenter: AdsPresenter
+ @Inject
+ lateinit var adsHelper: AdsHelper
+
override val titleStringId = R.string.pref_settings_ads_title
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -46,11 +47,18 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
true
}
- findPreference(getString(R.string.pref_key_ads_consent_data_processing))
- ?.setOnPreferenceChangeListener { _, newValue ->
- presenter.onConsentSelected(newValue as Boolean)
- true
- }
+ findPreference(getString(R.string.pref_key_ads_ump_agreements))?.setOnPreferenceClickListener {
+ presenter.onUmpAgreementsSelected()
+ true
+ }
+
+ findPreference(getString(R.string.pref_key_ads_single_support))
+ ?.isEnabled = adsHelper.canShowAd
+
+ findPreference(getString(R.string.pref_key_ads_enabled))?.setOnPreferenceChangeListener { _, newValue ->
+ presenter.onAdsEnabledSelected(newValue as Boolean)
+ true
+ }
}
override fun showAd(ad: RewardedInterstitialAd) {
@@ -59,48 +67,6 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
}
}
- override fun showPrivacyPolicyDialog() {
- val dialogAdsConsentBinding = DialogAdsConsentBinding.inflate(layoutInflater)
-
- val dialog = MaterialAlertDialogBuilder(requireContext())
- .setTitle(R.string.pref_ads_consent_title)
- .setMessage(R.string.pref_ads_consent_description)
- .setView(dialogAdsConsentBinding.root)
- .setOnCancelListener { presenter.onPrivacyDialogCanceled() }
- .show()
-
- dialogAdsConsentBinding.adsConsentOver.setOnCheckedChangeListener { _, isChecked ->
- dialogAdsConsentBinding.adsConsentPersonalised.isEnabled = isChecked
- }
-
- dialogAdsConsentBinding.adsConsentPersonalised.setOnClickListener {
- presenter.onPersonalizedAgree()
- dialog.dismiss()
- }
-
- dialogAdsConsentBinding.adsConsentNonPersonalised.setOnClickListener {
- presenter.onNonPersonalizedAgree()
- dialog.dismiss()
- }
-
- dialogAdsConsentBinding.adsConsentPrivacy.setOnClickListener { presenter.onPrivacySelected() }
- dialogAdsConsentBinding.adsConsentCancel.setOnClickListener { dialog.cancel() }
- }
-
- override fun showProcessingDataSummary(isPersonalized: Boolean?) {
- val summaryText = isPersonalized?.let {
- getString(if (it) R.string.pref_ads_summary_personalized else R.string.pref_ads_summary_non_personalized)
- }
-
- findPreference(getString(R.string.pref_key_ads_consent_data_processing))
- ?.summary = summaryText
- }
-
- override fun setCheckedProcessingData(checked: Boolean) {
- findPreference(getString(R.string.pref_key_ads_consent_data_processing))
- ?.isChecked = checked
- }
-
override fun setCheckedAdsEnabled(checked: Boolean) {
findPreference(getString(R.string.pref_key_ads_enabled))
?.isChecked = checked
@@ -135,8 +101,16 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
(activity as? BaseActivity<*, *>)?.showMessage(text)
}
- override fun showExpiredDialog() {
- (activity as? BaseActivity<*, *>)?.showExpiredDialog()
+ override fun showExpiredCredentialsDialog() {
+ (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
+ }
+
+ override fun onCaptchaVerificationRequired(url: String?) {
+ (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
+ }
+
+ override fun showDecryptionFailedDialog() {
+ (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
}
override fun showChangePasswordSnackbar(redirectUrl: String) {
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 28c98e3c3..b3c701541 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
@@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.settings.ads
-import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
@@ -13,18 +12,12 @@ class AdsPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val adsHelper: AdsHelper,
- private val preferencesRepository: PreferencesRepository
) : BasePresenter(errorHandler, studentRepository) {
override fun onAttachView(view: AdsView) {
super.onAttachView(view)
view.initView()
Timber.i("Settings ads view was initialized")
-
- view.showProcessingDataSummary(
- preferencesRepository.isPersonalizedAdsEnabled.takeIf {
- preferencesRepository.isAgreeToProcessData
- })
}
fun onWatchSingleAdSelected() {
@@ -50,38 +43,17 @@ class AdsPresenter @Inject constructor(
}
}
- fun onConsentSelected(isChecked: Boolean) {
- if (isChecked) {
- view?.showPrivacyPolicyDialog()
- } else {
- view?.showProcessingDataSummary(null)
- view?.setCheckedAdsEnabled(false)
- }
- }
-
fun onPrivacySelected() {
view?.openPrivacyPolicy()
}
- fun onPrivacyDialogCanceled() {
- view?.setCheckedProcessingData(false)
+ fun onAdsEnabledSelected(newValue: Boolean) {
+ if (newValue) {
+ adsHelper.initialize()
+ }
}
- fun onNonPersonalizedAgree() {
- preferencesRepository.isPersonalizedAdsEnabled = false
-
- adsHelper.initialize()
-
- view?.setCheckedProcessingData(true)
- view?.showProcessingDataSummary(false)
- }
-
- fun onPersonalizedAgree() {
- preferencesRepository.isPersonalizedAdsEnabled = true
-
- adsHelper.initialize()
-
- view?.setCheckedProcessingData(true)
- view?.showProcessingDataSummary(true)
+ fun onUmpAgreementsSelected() {
+ adsHelper.openAdsUmpAgreements()
}
}
diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt
index 8de6e60d3..3b3fa5783 100644
--- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt
+++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt
@@ -9,8 +9,6 @@ interface AdsView : BaseView {
fun showAd(ad: RewardedInterstitialAd)
- fun showPrivacyPolicyDialog()
-
fun openPrivacyPolicy()
fun showLoadingSupportAd(show: Boolean)
@@ -18,8 +16,4 @@ interface AdsView : BaseView {
fun showWatchAdOncePerVisit(show: Boolean)
fun setCheckedAdsEnabled(checked: Boolean)
-
- fun setCheckedProcessingData(checked: Boolean)
-
- fun showProcessingDataSummary(isPersonalized: Boolean?)
}
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 d5f65b46d..bd17d52c1 100644
--- a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt
+++ b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt
@@ -1,49 +1,110 @@
package io.github.wulkanowy.utils
+import android.app.Activity
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.AdListener
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.AdSize
+import com.google.android.gms.ads.AdView
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.MobileAds
import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoadCallback
+import com.google.android.ump.ConsentInformation
+import com.google.android.ump.ConsentRequestParameters
+import com.google.android.ump.UserMessagingPlatform
import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.android.scopes.ActivityScoped
import io.github.wulkanowy.BuildConfig
import io.github.wulkanowy.data.repositories.PreferencesRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import timber.log.Timber
import java.net.UnknownHostException
+import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
-
+@ActivityScoped
class AdsHelper @Inject constructor(
+ private val activity: Activity,
@ApplicationContext private val context: Context,
- private val preferencesRepository: PreferencesRepository
+ preferencesRepository: PreferencesRepository
) {
+ private var isMobileAdsInitializeCalled = AtomicBoolean(false)
+ private var consentInformation: ConsentInformation? = null
+
+ private val canRequestAd get() = consentInformation?.canRequestAds() == true
+ val isMobileAdsSdkInitialized = MutableStateFlow(false)
+ val canShowAd get() = isMobileAdsSdkInitialized.value && canRequestAd
+
+ init {
+ if (preferencesRepository.isAdsEnabled) {
+ initialize()
+ }
+ }
+
fun initialize() {
- if (preferencesRepository.isAgreeToProcessData) {
- MobileAds.initialize(context)
+ val consentRequestParameters = ConsentRequestParameters.Builder()
+ .build()
+
+ consentInformation = UserMessagingPlatform.getConsentInformation(context)
+ consentInformation?.requestConsentInfoUpdate(
+ activity,
+ consentRequestParameters,
+ {
+ UserMessagingPlatform.loadAndShowConsentFormIfRequired(
+ activity
+ ) { loadAndShowError ->
+
+ if (loadAndShowError != null) {
+ Timber.e(IllegalStateException("${loadAndShowError.errorCode}: ${loadAndShowError.message}"))
+ }
+
+ if (canRequestAd) {
+ initializeMobileAds()
+ }
+ }
+ },
+ { requestConsentError ->
+ Timber.e(IllegalStateException("${requestConsentError.errorCode}: ${requestConsentError.message}"))
+ })
+
+ if (canRequestAd) {
+ initializeMobileAds()
+ }
+ }
+
+ fun openAdsUmpAgreements() {
+ UserMessagingPlatform.showPrivacyOptionsForm(activity) {
+ if (it != null) {
+ Timber.e(IllegalStateException("${it.errorCode}: ${it.message}"))
+ }
+ }
+ }
+
+ private fun initializeMobileAds() {
+ if (isMobileAdsInitializeCalled.getAndSet(true)) return
+
+ MobileAds.initialize(context) {
+ isMobileAdsSdkInitialized.value = true
}
}
suspend fun getSupportAd(): RewardedInterstitialAd? {
+ if (!canRequestAd) return null
if (!context.isInternetConnected()) {
throw UnknownHostException()
}
- val extra = Bundle().apply { putString("npa", "1") }
val adRequest = AdRequest.Builder()
- .apply {
- if (!preferencesRepository.isPersonalizedAdsEnabled) {
- addNetworkExtrasBundle(AdMobAdapter::class.java, extra)
- }
- }
.build()
return suspendCoroutine {
@@ -64,13 +125,8 @@ class AdsHelper @Inject constructor(
}
suspend fun getDashboardTileAdBanner(width: Int): AdBanner {
- val extra = Bundle().apply { putString("npa", "1") }
+ if (!canShowAd) throw IllegalStateException("Cannot show ad")
val adRequest = AdRequest.Builder()
- .apply {
- if (!preferencesRepository.isPersonalizedAdsEnabled) {
- addNetworkExtrasBundle(AdMobAdapter::class.java, extra)
- }
- }
.build()
return suspendCoroutine {
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 3215fa20c..9ded7e1b6 100644
--- a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
+++ b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
@@ -1,25 +1,24 @@
package io.github.wulkanowy.utils
import android.app.Activity
-import android.content.Context
import android.os.Bundle
+import com.google.firebase.Firebase
import com.google.firebase.analytics.FirebaseAnalytics
-import com.google.firebase.crashlytics.FirebaseCrashlytics
-import dagger.hilt.android.qualifiers.ApplicationContext
+import com.google.firebase.analytics.analytics
+import com.google.firebase.crashlytics.crashlytics
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,
preferencesRepository: PreferencesRepository,
appInfo: AppInfo,
) {
- private val analytics by lazy { FirebaseAnalytics.getInstance(context) }
+ private val analytics by lazy { Firebase.analytics }
- private val crashlytics by lazy { FirebaseCrashlytics.getInstance() }
+ private val crashlytics by lazy { Firebase.crashlytics }
init {
if (!appInfo.isDebug) {
diff --git a/app/src/play/res/xml/scheme_preferences_ads.xml b/app/src/play/res/xml/scheme_preferences_ads.xml
index 4165561a7..444dde3e4 100644
--- a/app/src/play/res/xml/scheme_preferences_ads.xml
+++ b/app/src/play/res/xml/scheme_preferences_ads.xml
@@ -1,5 +1,11 @@
+
@@ -8,25 +14,17 @@
app:key="@string/pref_key_ads_privacy_policy"
app:singleLineTitle="false"
app:title="@string/pref_ads_privacy_policy" />
-
-
() to listOf(
- getSummary(
- 24,
- "Język polski",
- .0
- )
+ Triple(
+ emptyList(), listOf(
+ getSummary(
+ 24,
+ "Język polski",
+ .0
+ )
+ ), emptyList()
)
}
@@ -372,14 +428,22 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { emptyList() to emptyList() }
+ } returns resourceFlow {
+ Triple(
+ emptyList(),
+ emptyList(),
+ emptyList()
+ )
+ }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { emptyList() to emptyList() }
+ } returns resourceFlow {
+ Triple(emptyList(), emptyList(), emptyList())
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -404,7 +468,13 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -438,7 +508,13 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -472,7 +548,13 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -506,7 +588,13 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier }
+ } returns resourceFlow {
+ Triple(
+ secondGradeWithModifier,
+ secondSummariesWithModifier,
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -534,7 +622,7 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries }
+ } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -564,7 +652,7 @@ class GradeAverageProviderTest {
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries }
+ } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -594,7 +682,7 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries }
+ } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -625,8 +713,8 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow {
emit(Resource.Loading())
- emit(Resource.Intermediate(firstGrades to firstSummaries))
- emit(Resource.Success(firstGrades to firstSummaries))
+ emit(Resource.Intermediate(Triple(firstGrades, firstSummaries, emptyList())))
+ emit(Resource.Success(Triple(firstGrades, firstSummaries, emptyList())))
}
val items = runBlocking {
@@ -675,9 +763,11 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- firstGrades to listOf(
- getSummary(22, "Matematyka", 3.0),
- getSummary(22, "Fizyka", 3.5)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", 3.0),
+ getSummary(22, "Fizyka", 3.5)
+ ), emptyList()
)
}
coEvery {
@@ -687,9 +777,13 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- secondGrades to listOf(
- getSummary(22, "Matematyka", 3.5),
- getSummary(22, "Fizyka", 4.0)
+ Triple(
+ secondGrades,
+ listOf(
+ getSummary(22, "Matematyka", 3.5),
+ getSummary(22, "Fizyka", 4.0)
+ ),
+ emptyList()
)
}
@@ -723,17 +817,21 @@ class GradeAverageProviderTest {
emit(Resource.Loading())
emit(
Resource.Intermediate(
- firstGrades to listOf(
- getSummary(22, "Matematyka", 3.0),
- getSummary(22, "Fizyka", 3.5)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", 3.0),
+ getSummary(22, "Fizyka", 3.5)
+ ), emptyList()
)
)
)
emit(
Resource.Success(
- firstGrades to listOf(
- getSummary(22, "Matematyka", 3.0),
- getSummary(22, "Fizyka", 3.5)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", 3.0),
+ getSummary(22, "Fizyka", 3.5)
+ ), emptyList()
)
)
)
@@ -742,17 +840,21 @@ class GradeAverageProviderTest {
emit(Resource.Loading())
emit(
Resource.Intermediate(
- secondGrades to listOf(
- getSummary(22, "Matematyka", 3.5),
- getSummary(22, "Fizyka", 4.0)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 3.5),
+ getSummary(22, "Fizyka", 4.0)
+ ), emptyList()
)
)
)
emit(
Resource.Success(
- secondGrades to listOf(
- getSummary(22, "Matematyka", 3.5),
- getSummary(22, "Fizyka", 4.0)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 3.5),
+ getSummary(22, "Fizyka", 4.0)
+ ), emptyList()
)
)
)
@@ -803,7 +905,7 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries }
+ } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
coEvery {
gradeRepository.getGrades(
student,
@@ -811,9 +913,11 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- secondGrades to listOf(
- getSummary(22, "Matematyka", 1.1),
- getSummary(22, "Fizyka", 7.26)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 1.1),
+ getSummary(22, "Fizyka", 7.26)
+ ), emptyList()
)
}
@@ -850,9 +954,11 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- firstGrades to listOf(
- getSummary(22, "Matematyka", .0),
- getSummary(22, "Fizyka", .0)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", .0),
+ getSummary(22, "Fizyka", .0)
+ ), emptyList()
)
}
coEvery {
@@ -862,9 +968,11 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- secondGrades to listOf(
- getSummary(22, "Matematyka", .0),
- getSummary(22, "Fizyka", .0)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", .0),
+ getSummary(22, "Fizyka", .0)
+ ), emptyList()
)
}
@@ -889,24 +997,28 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow {
emit(Resource.Loading())
- emit(Resource.Intermediate(firstGrades to firstSummaries))
- emit(Resource.Success(firstGrades to firstSummaries))
+ emit(Resource.Intermediate(Triple(firstGrades, firstSummaries, emptyList())))
+ emit(Resource.Success(Triple(firstGrades, firstSummaries, emptyList())))
}
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow {
emit(Resource.Loading())
emit(
Resource.Intermediate(
- secondGrades to listOf(
- getSummary(22, "Matematyka", 1.1),
- getSummary(22, "Fizyka", 7.26)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 1.1),
+ getSummary(22, "Fizyka", 7.26)
+ ), emptyList()
)
)
)
emit(
Resource.Success(
- secondGrades to listOf(
- getSummary(22, "Matematyka", 1.1),
- getSummary(22, "Fizyka", 7.26)
+ Triple(
+ secondGrades, listOf(
+ getSummary(22, "Matematyka", 1.1),
+ getSummary(22, "Fizyka", 7.26)
+ ), emptyList()
)
)
)
@@ -958,14 +1070,14 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to emptyList() }
+ } returns resourceFlow { Triple(firstGrades, emptyList(), emptyList()) }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to emptyList() }
+ } returns resourceFlow { Triple(secondGrades, emptyList(), emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1000,14 +1112,14 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to emptyList() }
+ } returns resourceFlow { Triple(firstGrades, emptyList(), emptyList()) }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to emptyList() }
+ } returns resourceFlow { Triple(secondGrades, emptyList(), emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1043,8 +1155,10 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- firstGrades to listOf(
- getSummary(22, "Matematyka", 4.0)
+ Triple(
+ firstGrades, listOf(
+ getSummary(22, "Matematyka", 4.0)
+ ), emptyList()
)
}
coEvery {
@@ -1054,8 +1168,10 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- secondGrades to listOf(
- getSummary(23, "Matematyka", 3.0)
+ Triple(
+ secondGrades, listOf(
+ getSummary(23, "Matematyka", 3.0)
+ ), emptyList()
)
}
@@ -1092,14 +1208,20 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries }
+ } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries.dropLast(1) }
+ } returns resourceFlow {
+ Triple(
+ secondGrades,
+ secondSummaries.dropLast(1),
+ emptyList()
+ )
+ }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1134,14 +1256,20 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) }
+ } returns resourceFlow {
+ Triple(
+ firstGrades,
+ firstSummaries.dropLast(1),
+ emptyList()
+ )
+ }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries }
+ } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1176,14 +1304,20 @@ class GradeAverageProviderTest {
semesters[1],
true
)
- } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) }
+ } returns resourceFlow {
+ Triple(
+ firstGrades,
+ firstSummaries.dropLast(1),
+ emptyList()
+ )
+ }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
- } returns resourceFlow { secondGrades to secondSummaries }
+ } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
@@ -1219,16 +1353,20 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- listOf(
- getGrade(22, "Fizyka", 5.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 5.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0)
- ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(22, "Fizyka", 5.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 5.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
coEvery {
gradeRepository.getGrades(
@@ -1237,11 +1375,15 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- listOf(
- getGrade(23, "Fizyka", 5.0, weight = 1.0),
- getGrade(23, "Fizyka", 5.0, weight = 2.0),
- getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
- ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(23, "Fizyka", 5.0, weight = 1.0),
+ getGrade(23, "Fizyka", 5.0, weight = 2.0),
+ getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
val items = runBlocking {
@@ -1266,23 +1408,31 @@ class GradeAverageProviderTest {
every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR)
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow {
- listOf(
- getGrade(22, "Fizyka", 5.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 5.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0)
- ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(22, "Fizyka", 5.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 5.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow {
- listOf(
- getGrade(23, "Fizyka", 5.0, weight = 1.0),
- getGrade(23, "Fizyka", 5.0, weight = 2.0),
- getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
- ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(23, "Fizyka", 5.0, weight = 1.0),
+ getGrade(23, "Fizyka", 5.0, weight = 2.0),
+ getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
val items = runBlocking {
@@ -1313,23 +1463,31 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow {
- listOf(
- getGrade(22, "Fizyka", 5.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 5.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0)
- ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(22, "Fizyka", 5.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 5.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow {
- listOf(
- getGrade(23, "Fizyka", 5.0, weight = 1.0),
- getGrade(23, "Fizyka", 5.0, weight = 2.0),
- getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
- ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(23, "Fizyka", 5.0, weight = 1.0),
+ getGrade(23, "Fizyka", 5.0, weight = 2.0),
+ getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
val items = runBlocking {
@@ -1366,16 +1524,20 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- listOf(
- getGrade(22, "Fizyka", 5.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 5.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 4.0),
- getGrade(22, "Fizyka", 6.0, weight = 2.0)
- ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(22, "Fizyka", 5.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 5.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 4.0),
+ getGrade(22, "Fizyka", 6.0, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
coEvery {
gradeRepository.getGrades(
@@ -1384,11 +1546,15 @@ class GradeAverageProviderTest {
true
)
} returns resourceFlow {
- listOf(
- getGrade(23, "Fizyka", 5.0, weight = 1.0),
- getGrade(23, "Fizyka", 5.0, weight = 2.0),
- getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
- ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))
+ Triple(
+ listOf(
+ getGrade(23, "Fizyka", 5.0, weight = 1.0),
+ getGrade(23, "Fizyka", 5.0, weight = 2.0),
+ getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
+ ),
+ listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
+ emptyList()
+ )
}
val items = runBlocking {
@@ -1413,9 +1579,9 @@ class GradeAverageProviderTest {
every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false)
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns
- resourceFlow { firstGrades to firstSummaries }
+ resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns
- resourceFlow { listOf() to firstSummaries }
+ resourceFlow { Triple(listOf(), firstSummaries, emptyList()) }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(
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 fad6436d8..34965f00d 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
@@ -58,7 +58,7 @@ class LoginStudentSelectPresenterTest {
login = "",
password = "",
baseUrl = "",
- symbol = null,
+ defaultSymbol = "warszawa",
domainSuffix = "",
)
diff --git a/build.gradle b/build.gradle
index 3fafd2d24..f7f3d209e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,8 +1,8 @@
buildscript {
ext {
- kotlin_version = '1.9.10'
- about_libraries = '10.9.1'
- hilt_version = '2.48.1'
+ kotlin_version = '1.9.22'
+ about_libraries = '10.10.0'
+ hilt_version = '2.50'
}
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.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.13"
- classpath 'com.android.tools.build:gradle:8.1.2'
+ classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16"
+ classpath 'com.android.tools.build:gradle:8.2.2'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
- classpath 'com.google.gms:google-services:4.4.0'
- classpath 'com.huawei.agconnect:agcp:1.9.1.301'
+ classpath 'com.google.gms:google-services:4.4.1'
+ classpath 'com.huawei.agconnect:agcp:1.9.1.303'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
classpath "com.github.triplet.gradle:play-publisher:3.8.4"
- classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0"
+ classpath "ru.cian:huawei-publish-gradle-plugin:1.4.2"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.4.1.3373"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"
@@ -29,6 +29,7 @@ buildscript {
allprojects {
repositories {
+ mavenLocal()
mavenCentral()
google()
maven { url "https://jitpack.io" }
@@ -36,7 +37,3 @@ allprojects {
maven { url "https://developer.huawei.com/repo/" }
}
}
-
-tasks.register('clean', Delete) {
- delete rootProject.buildDir
-}
diff --git a/gradle.properties b/gradle.properties
index 4c54d414a..99305ac50 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,8 +6,9 @@ kotlin.code.style=official
android.useAndroidX=true
android.enableJetifier=true
android.nonTransitiveRClass=false
-android.defaults.buildfeatures.buildconfig=true
#
# https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-common-faq-0000001063210244#section17273113244910
apmsInstrumentationEnabled=false
+# https://community.sonarsource.com/t/sonarscanner-for-gradle-you-can-now-decide-when-to-compile/102069/2
+systemProp.sonar.gradle.skipCompile=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 033e24c4c..7f93135c4 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 8838ba97b..e6aba2515 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index fcb6fca14..1aa94a426 100755
--- a/gradlew
+++ b/gradlew
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -201,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \