diff --git a/.circleci/config.yml b/.circleci/config.yml
index ce2922ba..2cb2e147 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -162,7 +162,7 @@ jobs:
openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks
- run:
name: Publish release
- command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex
+ command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PdisablePreDex
workflows:
version: 2
diff --git a/.github/workflows/deploy-store.yml b/.github/workflows/deploy-store.yml
index e8237a38..0195f3e5 100644
--- a/.github/workflows/deploy-store.yml
+++ b/.github/workflows/deploy-store.yml
@@ -40,7 +40,7 @@ jobs:
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
- run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
+ run: ./gradlew publishPlayReleaseApps --stacktrace;
deploy-app-gallery:
name: AppGallery
diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml
index c4f55e6a..42c1f8e7 100644
--- a/.github/workflows/deploy-test.yml
+++ b/.github/workflows/deploy-test.yml
@@ -36,8 +36,7 @@ jobs:
- name: Prepare build configuration
run: |
sed -i -e "s#applicationIdSuffix \".dev\"#applicationIdSuffix \".${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/build.gradle
- sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/src/debug/google-services.json
- sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/src/debug/agconnect-services.json
+ sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/google-services.json
sed -i -e '/versionNameSuffix/d' app/build.gradle
- name: Add signing config
run: |
@@ -131,7 +130,7 @@ jobs:
BITRISE_KEYSTORE_PASSWORD: ${{ secrets.BITRISE_KEYSTORE_PASSWORD }}
BITRISE_KEY_ALIAS: ${{ secrets.BITRISE_KEY_ALIAS }}
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
- run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace
+ run: ./gradlew assemblePlayDebug --stacktrace
- name: Upload apk to github artifacts
uses: actions/upload-artifact@v3
with:
diff --git a/.gitignore b/.gitignore
index 980085e3..ad83ced8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,6 +67,10 @@ captures/
.idea/discord.xml
.idea/migrations.xml
.idea/androidTestResultsUserPreferences.xml
+.idea/copilot
+.idea/deploymentTargetDropDown.xml
+.idea/deploymentTargetSelector.xml
+.idea/kotlinc.xml
# Keystore files
*.jks
@@ -113,12 +117,13 @@ Thumbs.db
*.ear
### AndroidStudio Patch ###
-
!/gradle/wrapper/gradle-wrapper.jar
.idea/jarRepositories.xml
+### Services config files
+agconnect-services.json
+agconnect-credentials.json
+google-services.json
+!app/google-services.json
+
-app/src/release/agconnect-services.json
-app/src/release/agconnect-credentials.json
-.idea/deploymentTargetDropDown.xml
-.idea/kotlinc.xml
diff --git a/.travis.yml b/.travis.yml
index 04db3a61..e0b0be97 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -61,7 +61,7 @@ script:
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
- ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
+ ./gradlew publishPlayRelease --stacktrace;
fi
after_success:
diff --git a/app/build.gradle b/app/build.gradle
index 07efeb2f..07719dfd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,15 +27,12 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
- versionCode 148
- versionName "2.4.2"
+ versionCode 151
+ versionName "2.5.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
- manifestPlaceholders = [
- firebase_enabled: project.hasProperty("enableFirebase"),
- admob_project_id: ""
- ]
+ manifestPlaceholders = [admob_project_id: ""]
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
@@ -76,7 +73,6 @@ android {
resValue "string", "app_name", "Wulkanowy DEV"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
- ext.enableCrashlytics = project.hasProperty("enableFirebase")
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"'
}
@@ -164,8 +160,8 @@ play {
defaultToAppBundles = false
track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS
- userFraction = 0.99d
- updatePriority = 2
+ userFraction = 0.50d
+ updatePriority = 3
enabled.set(false)
}
@@ -190,12 +186,12 @@ ext {
android_hilt = "1.2.0"
room = "2.6.1"
chucker = "4.0.0"
- mockk = "1.13.9"
+ mockk = "1.13.10"
coroutines = "1.8.0"
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.4.2-SNAPSHOT'
+ implementation 'io.github.wulkanowy:sdk:2.5.2'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
@@ -252,13 +248,13 @@ dependencies {
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
implementation 'org.apache.commons:commons-text:1.11.0'
- playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
+ playImplementation platform('com.google.firebase:firebase-bom:32.7.4')
playImplementation 'com.google.firebase:firebase-analytics'
playImplementation 'com.google.firebase:firebase-messaging'
playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.firebase:firebase-config'
- playImplementation 'com.google.android.gms:play-services-ads:22.6.0'
+ playImplementation 'com.google.android.gms:play-services-ads:23.0.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'
diff --git a/app/src/debug/google-services.json b/app/google-services.json
similarity index 100%
rename from app/src/debug/google-services.json
rename to app/google-services.json
diff --git a/app/play-publish-lint.sh b/app/play-publish-lint.sh
index d3354b1a..5f0391de 100755
--- a/app/play-publish-lint.sh
+++ b/app/play-publish-lint.sh
@@ -1,7 +1,8 @@
#!/bin/bash -
content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit
-if [[ "${#content}" -gt 500 ]]; then
+content2=echo "$content" | dos2unix
+if [[ "${#content2}" -gt 500 ]]; then
echo >&2 "Release notes content has reached the limit of 500 characters"
exit 1
fi
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json
new file mode 100644
index 00000000..e36dcc8a
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json
@@ -0,0 +1,2533 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 61,
+ "identityHash": "41fbd2ff00aba10b2ef0a079e6037c87",
+ "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, `author` TEXT, `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": "author",
+ "columnName": "author",
+ "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": "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, '41fbd2ff00aba10b2ef0a079e6037c87')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json
new file mode 100644
index 00000000..ab63c679
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json
@@ -0,0 +1,2547 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 62,
+ "identityHash": "ee2464d218b254ca868667c0fc756c0b",
+ "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, `is_authorized` INTEGER NOT NULL DEFAULT 0, `is_edu_one` INTEGER NOT NULL DEFAULT 0, `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": "isAuthorized",
+ "columnName": "is_authorized",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isEduOne",
+ "columnName": "is_edu_one",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "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, `author` TEXT, `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": "author",
+ "columnName": "author",
+ "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": "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, 'ee2464d218b254ca868667c0fc756c0b')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/debug/agconnect-services.json b/app/src/debug/agconnect-services.json
deleted file mode 100644
index 52426f54..00000000
--- a/app/src/debug/agconnect-services.json
+++ /dev/null
@@ -1,92 +0,0 @@
-{
- "agcgw": {
- "backurl": "connect-dre.hispace.hicloud.com",
- "url": "connect-dre.dbankcloud.cn",
- "websocketbackurl": "connect-ws-dre.hispace.dbankcloud.com",
- "websocketurl": "connect-ws-dre.hispace.dbankcloud.cn"
- },
- "agcgw_all": {
- "CN": "connect-drcn.dbankcloud.cn",
- "CN_back": "connect-drcn.hispace.hicloud.com",
- "DE": "connect-dre.dbankcloud.cn",
- "DE_back": "connect-dre.hispace.hicloud.com",
- "RU": "connect-drru.hispace.dbankcloud.ru",
- "RU_back": "connect-drru.hispace.dbankcloud.cn",
- "SG": "connect-dra.dbankcloud.cn",
- "SG_back": "connect-dra.hispace.hicloud.com"
- },
- "websocketgw_all": {
- "CN": "connect-ws-drcn.hispace.dbankcloud.cn",
- "CN_back": "connect-ws-drcn.hispace.dbankcloud.com",
- "DE": "connect-ws-dre.hispace.dbankcloud.cn",
- "DE_back": "connect-ws-dre.hispace.dbankcloud.com",
- "RU": "connect-ws-drru.hispace.dbankcloud.ru",
- "RU_back": "connect-ws-drru.hispace.dbankcloud.cn",
- "SG": "connect-ws-dra.hispace.dbankcloud.cn",
- "SG_back": "connect-ws-dra.hispace.dbankcloud.com"
- },
- "client": {
- "cp_id": "890048000024105546",
- "product_id": "736430079244736562",
- "client_id": "514530959291319360",
- "client_secret": "C42522DBF17D3D4BBE9D9C1783A54484B7E6844B388B7A67502D36A633A4186B",
- "project_id": "736430079244736562",
- "app_id": "106552551",
- "api_key": "CgB6e3x9BUNiq+r8ebCHNojjjYsMT4pJSjjNDOkm9owtBb6rVI6LjnASoZBRxbjjhObcrV5gANo99fI/eKZDTbWS",
- "package_name": "io.github.wulkanowy.dev"
- },
- "oauth_client": {
- "client_id": "106552551",
- "client_type": 1
- },
- "app_info": {
- "app_id": "106552551",
- "package_name": "io.github.wulkanowy.dev"
- },
- "service": {
- "analytics": {
- "collector_url": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
- "collector_url_ru": "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
- "collector_url_sg": "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
- "collector_url_de": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
- "collector_url_cn": "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
- "resource_id": "p1",
- "channel_id": ""
- },
- "search":{
- "url":"https://search-dre.cloud.huawei.com"
- },
- "cloudstorage": {
- "storage_url_sg_back": "https://agc-storage-dra.cloud.huawei.asia",
- "storage_url_ru_back": "https://agc-storage-drru.cloud.huawei.ru",
- "storage_url_ru": "https://agc-storage-drru.cloud.huawei.ru",
- "storage_url_de_back": "https://agc-storage-dre.cloud.huawei.eu",
- "storage_url_de": "https://ops-dre.agcstorage.link",
- "storage_url": "https://agc-storage-drcn.platform.dbankcloud.cn",
- "storage_url_sg": "https://ops-dra.agcstorage.link",
- "storage_url_cn_back": "https://agc-storage-drcn.cloud.huawei.com.cn",
- "storage_url_cn": "https://agc-storage-drcn.platform.dbankcloud.cn"
- },
- "ml": {
- "mlservice_url": "ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
- }
- },
- "region": "DE",
- "configuration_version": "3.0",
- "appInfos": [
- {
- "package_name": "io.github.wulkanowy.dev",
- "client": {
- "app_id": "106552551"
- },
- "app_info": {
- "package_name": "io.github.wulkanowy.dev",
- "app_id": "106552551"
- },
- "oauth_client": {
- "client_type": 1,
- "client_id": "106552551"
- }
- }
- ]
-}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f43dfdd2..4e617c93 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -155,33 +155,9 @@
android:resource="@xml/provider_paths" />
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
index 6b6c9d32..a492c08d 100644
--- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
@@ -18,17 +18,13 @@ import io.github.wulkanowy.data.api.SchoolsService
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo
-import io.github.wulkanowy.utils.RemoteConfigHelper
-import io.github.wulkanowy.utils.WebkitCookieManagerProxy
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.create
-import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@@ -36,20 +32,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
internal class DataModule {
- @Singleton
- @Provides
- fun provideSdk(chuckerInterceptor: ChuckerInterceptor, remoteConfig: RemoteConfigHelper) =
- Sdk().apply {
- androidVersion = android.os.Build.VERSION.RELEASE
- buildTag = android.os.Build.MODEL
- userAgentTemplate = remoteConfig.userAgentTemplate
- setSimpleHttpLogger { Timber.d(it) }
- setAdditionalCookieManager(WebkitCookieManagerProxy())
-
- // for debug only
- addInterceptor(chuckerInterceptor, network = true)
- }
-
@Singleton
@Provides
fun provideChuckerCollector(
diff --git a/app/src/main/java/io/github/wulkanowy/data/Resource.kt b/app/src/main/java/io/github/wulkanowy/data/Resource.kt
index 108b0d58..712a946f 100644
--- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt
@@ -1,11 +1,17 @@
package io.github.wulkanowy.data
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -14,16 +20,39 @@ import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
-sealed class Resource {
-
- open class Loading : Resource()
+sealed interface Resource {
+ /**
+ * The initial value of a resource flow. Indicates no data that is currently available to be shown,
+ * however with the expectation that the state will transition to another one soon.
+ */
+ open class Loading : Resource
+ /**
+ * A semi-loading state with some data available to be displayed (usually cached data loaded from
+ * the database). Still not the target state and it's expected to transition into another one soon.
+ */
data class Intermediate(val data: T) : Loading()
- data class Success(val data: T) : Resource()
+ /**
+ * The happy-path target state. Data can either be:
+ * - loaded from the database - while it may seem like this case is already handled by the
+ * Intermediate state, the difference here is semantic. Cached data is returned as Intermediate
+ * when there's a API request in progress (or soon expected to be), however when there is no
+ * intention of immediately querying the API, the cached data is returned as a Success.
+ * - fetched from the API.
+ */
+ data class Success(val data: T) : Resource
- data class Error(val error: Throwable) : Resource()
+ /**
+ * Something bad happened and we were unable to get the requested data. This can be caused by
+ * a database error, a network error, or really just any other error. Upon receiving this state
+ * the UI can either: display a full screen error, or, when it has received any data previously,
+ * display a snack bar informing of the problem.
+ */
+ data class Error(val error: Throwable) : Resource
}
val Resource.dataOrNull: T?
@@ -64,6 +93,22 @@ fun Resource.mapData(block: (T) -> U) = when (this) {
is Resource.Error -> Resource.Error(this.error)
}
+/**
+ * Injects another flow into this flow's resource data.
+ */
+inline fun Flow>.combineWithResourceData(
+ flow: Flow,
+ crossinline block: suspend (T1, T2) -> R
+): Flow> =
+ combine(flow) { resource, inject ->
+ when (resource) {
+ is Resource.Success -> Resource.Success(block(resource.data, inject))
+ is Resource.Intermediate -> Resource.Intermediate(block(resource.data, inject))
+ is Resource.Loading -> Resource.Loading()
+ is Resource.Error -> Resource.Error(resource.error)
+ }
+ }
+
fun Flow>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
val description = when (it) {
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
@@ -74,8 +119,29 @@ fun Flow>.logResourceStatus(name: String, showData: Boolean = fa
Timber.i("$name: $description")
}
-fun Flow>.mapResourceData(block: (T) -> U) = map {
- it.mapData(block)
+inline fun Flow>.mapResourceData(crossinline block: suspend (T) -> U) = map {
+ when (it) {
+ is Resource.Success -> Resource.Success(block(it.data))
+ is Resource.Intermediate -> Resource.Intermediate(block(it.data))
+ is Resource.Loading -> Resource.Loading()
+ is Resource.Error -> Resource.Error(it.error)
+ }
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+fun Flow>.flatMapResourceData(
+ inheritIntermediate: Boolean = true, block: suspend (T) -> Flow>
+) = flatMapLatest {
+ when (it) {
+ is Resource.Success -> block(it.data)
+ is Resource.Intermediate -> block(it.data).map { newRes ->
+ if (inheritIntermediate && newRes is Resource.Success) Resource.Intermediate(newRes.data)
+ else newRes
+ }
+
+ is Resource.Loading -> flowOf(Resource.Loading())
+ is Resource.Error -> flowOf(Resource.Error(it.error))
+ }
}
fun Flow>.onResourceData(block: suspend (T) -> Unit) = onEach {
@@ -105,13 +171,13 @@ fun Flow>.onResourceSuccess(block: suspend (T) -> Unit) = onEach
}
}
-fun Flow>.onResourceError(block: (Throwable) -> Unit) = onEach {
+fun Flow>.onResourceError(block: suspend (Throwable) -> Unit) = onEach {
if (it is Resource.Error) {
block(it.error)
}
}
-fun Flow>.onResourceNotLoading(block: () -> Unit) = onEach {
+fun Flow>.onResourceNotLoading(block: suspend () -> Unit) = onEach {
if (it !is Resource.Loading) {
block()
}
@@ -121,70 +187,99 @@ suspend fun Flow>.toFirstResult() = filter { it !is Resource.Loa
suspend fun Flow>.waitForResult() = takeWhile { it is Resource.Loading }.collect()
-inline fun networkBoundResource(
- mutex: Mutex = Mutex(),
- showSavedOnLoading: Boolean = true,
- crossinline isResultEmpty: (ResultType) -> Boolean,
- crossinline query: () -> Flow,
- crossinline fetch: suspend (ResultType) -> RequestType,
- crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
- crossinline onFetchFailed: (Throwable) -> Unit = { },
- crossinline shouldFetch: (ResultType) -> Boolean = { true },
- crossinline filterResult: (ResultType) -> ResultType = { it }
-) = flow {
- emit(Resource.Loading())
+// Can cause excessive amounts of `Resource.Intermediate` to be emitted. Unless that is desired,
+// use `debounceIntermediates` to alleviate this behavior.
+inline fun combineResourceFlows(flows: Iterable>>): Flow>> =
+ combine(flows) { items ->
+ var isIntermediate = false
+ val data = mutableListOf()
+ for (item in items) {
+ when (item) {
+ is Resource.Success -> data.add(item.data)
+ is Resource.Intermediate -> {
+ isIntermediate = true
+ data.add(item.data)
+ }
- val data = query().first()
- emitAll(if (shouldFetch(data)) {
- val filteredResult = filterResult(data)
-
- if (showSavedOnLoading && !isResultEmpty(filteredResult)) {
- emit(Resource.Intermediate(filteredResult))
+ is Resource.Loading -> return@combine Resource.Loading()
+ is Resource.Error -> continue
+ }
}
-
- try {
- val newData = fetch(data)
- mutex.withLock { saveFetchResult(query().first(), newData) }
- query().map { Resource.Success(filterResult(it)) }
- } catch (throwable: Throwable) {
- onFetchFailed(throwable)
- flowOf(Resource.Error(throwable))
+ if (data.isEmpty()) {
+ // All items have to be errors for this to happen, so just return the first one.
+ // mapData is functionally useless and exists only to satisfy the type checker
+ items.first().mapData { listOf(it) }
+ } else if (isIntermediate) {
+ Resource.Intermediate(data)
+ } else {
+ Resource.Success(data)
+ }
+ }
+
+@OptIn(FlowPreview::class)
+fun Flow>.debounceIntermediates(timeout: Duration = 5.seconds) = flow {
+ var wasIntermediate = false
+
+ emitAll(this@debounceIntermediates.debounce {
+ if (it is Resource.Intermediate) {
+ if (!wasIntermediate) {
+ wasIntermediate = true
+ Duration.ZERO
+ } else {
+ timeout
+ }
+ } else {
+ wasIntermediate = false
+ Duration.ZERO
}
- } else {
- query().map { Resource.Success(filterResult(it)) }
})
}
+
+inline fun networkBoundResource(
+ mutex: Mutex = Mutex(),
+ crossinline isResultEmpty: (OutputType) -> Boolean,
+ crossinline query: () -> Flow,
+ crossinline fetch: suspend () -> ApiType,
+ crossinline saveFetchResult: suspend (old: OutputType, new: ApiType) -> Unit,
+ crossinline shouldFetch: (OutputType) -> Boolean = { true },
+ crossinline filterResult: (OutputType) -> OutputType = { it }
+) = networkBoundResource(
+ mutex = mutex,
+ isResultEmpty = isResultEmpty,
+ query = query,
+ fetch = fetch,
+ saveFetchResult = saveFetchResult,
+ shouldFetch = shouldFetch,
+ mapResult = filterResult
+)
+
@JvmName("networkBoundResourceWithMap")
-inline fun networkBoundResource(
+inline fun networkBoundResource(
mutex: Mutex = Mutex(),
- showSavedOnLoading: Boolean = true,
- crossinline isResultEmpty: (T) -> Boolean,
- crossinline query: () -> Flow,
- crossinline fetch: suspend (ResultType) -> RequestType,
- crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
- crossinline onFetchFailed: (Throwable) -> Unit = { },
- crossinline shouldFetch: (ResultType) -> Boolean = { true },
- crossinline mapResult: (ResultType) -> T,
+ crossinline isResultEmpty: (OutputType) -> Boolean,
+ crossinline query: () -> Flow,
+ crossinline fetch: suspend () -> ApiType,
+ crossinline saveFetchResult: suspend (old: DatabaseType, new: ApiType) -> Unit,
+ crossinline shouldFetch: (DatabaseType) -> Boolean = { true },
+ crossinline mapResult: (DatabaseType) -> OutputType,
) = flow {
emit(Resource.Loading())
val data = query().first()
- emitAll(if (shouldFetch(data)) {
- val mappedResult = mapResult(data)
+ if (shouldFetch(data)) {
+ emit(Resource.Intermediate(data))
- if (showSavedOnLoading && !isResultEmpty(mappedResult)) {
- emit(Resource.Intermediate(mappedResult))
- }
try {
- val newData = fetch(data)
+ val newData = fetch()
mutex.withLock { saveFetchResult(query().first(), newData) }
- query().map { Resource.Success(mapResult(it)) }
} catch (throwable: Throwable) {
- onFetchFailed(throwable)
- flowOf(Resource.Error(throwable))
+ emit(Resource.Error(throwable))
+ return@flow
}
- } else {
- query().map { Resource.Success(mapResult(it)) }
- })
+ }
+
+ emitAll(query().map { Resource.Success(it) })
}
+ .mapResourceData { mapResult(it) }
+ .filterNot { it is Resource.Intermediate && isResultEmpty(it.data) }
diff --git a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt
new file mode 100644
index 00000000..6496aeca
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt
@@ -0,0 +1,65 @@
+package io.github.wulkanowy.data
+
+import com.chuckerteam.chucker.api.ChuckerInterceptor
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.sdk.Sdk
+import io.github.wulkanowy.utils.RemoteConfigHelper
+import io.github.wulkanowy.utils.WebkitCookieManagerProxy
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class WulkanowySdkFactory @Inject constructor(
+ private val chuckerInterceptor: ChuckerInterceptor,
+ private val remoteConfig: RemoteConfigHelper,
+ private val webkitCookieManagerProxy: WebkitCookieManagerProxy
+) {
+
+ private val sdk = Sdk().apply {
+ androidVersion = android.os.Build.VERSION.RELEASE
+ buildTag = android.os.Build.MODEL
+ userAgentTemplate = remoteConfig.userAgentTemplate
+ setSimpleHttpLogger { Timber.d(it) }
+ setAdditionalCookieManager(webkitCookieManagerProxy)
+
+ // for debug only
+ addInterceptor(chuckerInterceptor, network = true)
+ }
+
+ fun create() = sdk
+
+ fun create(student: Student, semester: Semester? = null): Sdk {
+ return create().apply {
+ email = student.email
+ password = student.password
+ symbol = student.symbol
+ schoolSymbol = student.schoolSymbol
+ studentId = student.studentId
+ classId = student.classId
+ isEduOne = student.isEduOne
+ emptyCookieJarInterceptor = true
+
+ if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
+ mobileBaseUrl = student.mobileBaseUrl
+ } else {
+ scrapperBaseUrl = student.scrapperBaseUrl
+ domainSuffix = student.scrapperDomainSuffix
+ loginType = Sdk.ScrapperLoginType.valueOf(student.loginType)
+ }
+
+ mode = Sdk.Mode.valueOf(student.loginMode)
+ mobileBaseUrl = student.mobileBaseUrl
+ keyId = student.certificateKey
+ privatePem = student.privateKey
+
+ if (semester != null) {
+ diaryId = semester.diaryId
+ kindergartenDiaryId = semester.kindergartenDiaryId
+ schoolYear = semester.schoolYear
+ unitId = semester.unitId
+ }
+ }
+ }
+}
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 21a6e3f3..3189fc31 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
@@ -173,6 +173,8 @@ import javax.inject.Singleton
AutoMigration(from = 57, to = 58, spec = Migration58::class),
AutoMigration(from = 58, to = 59),
AutoMigration(from = 59, to = 60),
+ AutoMigration(from = 60, to = 61),
+ AutoMigration(from = 61, to = 62),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@@ -181,7 +183,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
- const val VERSION_SCHEMA = 60
+ const val VERSION_SCHEMA = 62
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@@ -308,6 +310,6 @@ abstract class AppDatabase : RoomDatabase() {
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/AdminMessageDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AdminMessageDao.kt
index 2b4cb597..6c8d7e47 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/AdminMessageDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AdminMessageDao.kt
@@ -2,24 +2,14 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
-import androidx.room.Transaction
import io.github.wulkanowy.data.db.entities.AdminMessage
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Singleton
@Dao
-abstract class AdminMessageDao : BaseDao {
+interface AdminMessageDao : BaseDao {
@Query("SELECT * FROM AdminMessages")
- abstract fun loadAll(): Flow>
-
- @Transaction
- open suspend fun removeOldAndSaveNew(
- oldMessages: List,
- newMessages: List
- ) {
- deleteAll(oldMessages)
- insertAll(newMessages)
- }
+ fun loadAll(): Flow>
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt
index 056a5cbd..937e9824 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
+import androidx.room.Transaction
import androidx.room.Update
interface BaseDao {
@@ -15,4 +16,10 @@ interface BaseDao {
@Delete
suspend fun deleteAll(items: List)
+
+ @Transaction
+ suspend fun removeOldAndSaveNew(oldItems: List, newItems: List) {
+ deleteAll(oldItems)
+ insertAll(newItems)
+ }
}
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 d9326ff6..95abb399 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
@@ -9,6 +9,7 @@ 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.StudentIsAuthorized
import io.github.wulkanowy.data.db.entities.StudentName
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import javax.inject.Singleton
@@ -23,6 +24,9 @@ abstract class StudentDao {
@Delete
abstract suspend fun delete(student: Student)
+ @Update(entity = Student::class)
+ abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized)
+
@Update(entity = Student::class)
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt
index 25e27ef1..ac096b02 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt
@@ -16,7 +16,9 @@ data class SchoolAnnouncement(
val subject: String,
- val content: String
+ val content: String,
+
+ val author: String? = null,
) : Serializable {
@PrimaryKey(autoGenerate = true)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt
index e1116733..23e4439a 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt
@@ -78,6 +78,13 @@ data class Student(
@ColumnInfo(name = "registration_date")
val registrationDate: Instant,
+
+ @ColumnInfo(name = "is_authorized", defaultValue = "0")
+ val isAuthorized: Boolean,
+
+ @ColumnInfo(name = "is_edu_one", defaultValue = "0")
+ val isEduOne: Boolean,
+
) : Serializable {
@PrimaryKey(autoGenerate = true)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt
new file mode 100644
index 00000000..a1290177
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt
@@ -0,0 +1,16 @@
+package io.github.wulkanowy.data.db.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.io.Serializable
+
+@Entity
+data class StudentIsAuthorized(
+ @ColumnInfo(name = "is_authorized", defaultValue = "0")
+ val isAuthorized: Boolean,
+) : Serializable {
+
+ @PrimaryKey
+ var id: Long = 0
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt
new file mode 100644
index 00000000..77dd5fc4
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt
@@ -0,0 +1,13 @@
+package io.github.wulkanowy.data.enums
+
+enum class AttendanceCalculatorSortingMode(private val value: String) {
+ ALPHABETIC("alphabetic"),
+ ATTENDANCE("attendance_percentage"),
+ LESSON_BALANCE("lesson_balance");
+
+ companion object {
+ fun getByValue(value: String) =
+ AttendanceCalculatorSortingMode.values()
+ .find { it.value == value } ?: ALPHABETIC
+ }
+}
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 899ba908..7cb4202a 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/DirectorInformationMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt
index 16f1bbac..85b37afc 100644
--- a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt
@@ -3,12 +3,26 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation
+import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement
+@JvmName("mapDirectorInformationToEntities")
fun List.mapToEntities(student: Student) = map {
SchoolAnnouncement(
userLoginId = student.userLoginId,
date = it.date,
subject = it.subject,
content = it.content,
+ author = null,
+ )
+}
+
+@JvmName("mapLastAnnouncementsToEntities")
+fun List.mapToEntities(student: Student) = map {
+ SchoolAnnouncement(
+ userLoginId = student.userLoginId,
+ date = it.date,
+ subject = it.subject,
+ content = it.content,
+ author = it.author,
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt
index 72c4861c..7e6a8166 100644
--- a/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt
@@ -34,17 +34,19 @@ fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser(
error = it.error,
students = it.subjects
.filterIsInstance()
- .map { registerSubject ->
+ .map { registerStudent ->
RegisterStudent(
- studentId = registerSubject.studentId,
- studentName = registerSubject.studentName,
- studentSecondName = registerSubject.studentSecondName,
- studentSurname = registerSubject.studentSurname,
- className = registerSubject.className,
- classId = registerSubject.classId,
- isParent = registerSubject.isParent,
- semesters = registerSubject.semesters
- .mapToEntities(registerSubject.studentId),
+ studentId = registerStudent.studentId,
+ studentName = registerStudent.studentName,
+ studentSecondName = registerStudent.studentSecondName,
+ studentSurname = registerStudent.studentSurname,
+ className = registerStudent.className,
+ classId = registerStudent.classId,
+ isParent = registerStudent.isParent,
+ isAuthorized = registerStudent.isAuthorized,
+ isEduOne = registerStudent.isEduOne,
+ semesters = registerStudent.semesters
+ .mapToEntities(registerStudent.studentId),
)
},
)
@@ -84,6 +86,8 @@ fun RegisterStudent.mapToStudentWithSemesters(
password = user.password.orEmpty(),
isCurrent = false,
registrationDate = Instant.now(),
+ isAuthorized = this.isAuthorized,
+ isEduOne = this.isEduOne,
).apply {
avatarColor = colors.random()
},
diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt
new file mode 100644
index 00000000..5810363c
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt
@@ -0,0 +1,14 @@
+package io.github.wulkanowy.data.pojos
+
+data class AttendanceData(
+ val subjectName: String,
+ val lessonBalance: Int,
+ val presences: Int,
+ val absences: Int,
+) {
+ val total: Int
+ get() = presences + absences
+
+ val presencePercentage: Double
+ get() = if (total == 0) 0.0 else (presences.toDouble() / total) * 100
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt
index 98bf1402..dec6ebec 100644
--- a/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt
@@ -45,4 +45,6 @@ data class RegisterStudent(
val classId: Int,
val isParent: Boolean,
val semesters: List,
+ val isAuthorized: Boolean,
+ val isEduOne: Boolean
) : java.io.Serializable
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt
index b831ee75..aa0022b0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt
@@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.networkBoundResource
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@@ -28,6 +29,6 @@ class AdminMessageRepository @Inject constructor(
saveFetchResult = { oldItems, newItems ->
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
},
- showSavedOnLoading = false,
)
+ .filterNot { it is Resource.Intermediate }
}
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 39043533..eb8656de 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
@@ -1,5 +1,6 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.entities.Attendance
@@ -7,20 +8,15 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
-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
@@ -31,10 +27,11 @@ import javax.inject.Singleton
class AttendanceRepository @Inject constructor(
private val attendanceDb: AttendanceDao,
private val timetableDb: TimetableDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
private val preferencesRepository: PreferencesRepository
) {
+
private val saveFetchResultMutex = Mutex()
private val cacheKey = "attendance"
@@ -81,27 +78,23 @@ class AttendanceRepository @Inject constructor(
.selectedHiddenSettingTiles
.contains(DashboardItem.HiddenSettingTile.NOTES)
- 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)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getAttendance(start.monday, end.sunday)
- .filter { attendance ->
- !badAttendanceHidden ||
- (attendance.presence || attendance.excused)
- }.mapToEntities(semester, lessons)
+ .filter { !badAttendanceHidden || (it.presence || it.excused) }
+ .mapToEntities(semester, lessons)
},
saveFetchResult = { old, new ->
- attendanceDb.deleteAll(old uniqueSubtract new)
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
newAttendance.apply { if (notify) isNotified = false }
}
- attendanceDb.insertAll(attendanceToAdd)
-
+ attendanceDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = attendanceToAdd,
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
},
filterResult = { it.filter { item -> item.date in start..end } }
@@ -128,8 +121,10 @@ class AttendanceRepository @Inject constructor(
}
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(
@@ -137,8 +132,7 @@ class AttendanceRepository @Inject constructor(
timeId = attendance.timeId
)
}
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, 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 6bdcf9d7..78c98169 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
@@ -1,15 +1,15 @@
package io.github.wulkanowy.data.repositories
+import androidx.room.withTransaction
+import io.github.wulkanowy.data.WulkanowySdkFactory
+import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.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
@@ -18,8 +18,9 @@ import javax.inject.Singleton
@Singleton
class AttendanceSummaryRepository @Inject constructor(
private val attendanceDb: AttendanceSummaryDao,
- private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper,
+ private val appDatabase: AppDatabase,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
) {
private val saveFetchResultMutex = Mutex()
@@ -40,14 +41,15 @@ class AttendanceSummaryRepository @Inject constructor(
},
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getAttendanceSummary(subjectId)
.mapToEntities(semester, subjectId)
},
saveFetchResult = { old, new ->
- attendanceDb.deleteAll(old uniqueSubtract new)
- attendanceDb.insertAll(new uniqueSubtract old)
+ appDatabase.withTransaction {
+ attendanceDb.deleteAll(old uniqueSubtract new)
+ attendanceDb.insertAll(new uniqueSubtract old)
+ }
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
}
)
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 1579ae62..45a36f55 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
@@ -1,12 +1,16 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
import javax.inject.Inject
@@ -15,7 +19,7 @@ import javax.inject.Singleton
@Singleton
class CompletedLessonsRepository @Inject constructor(
private val completedLessonsDb: CompletedLessonsDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -47,14 +51,15 @@ class CompletedLessonsRepository @Inject constructor(
)
},
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getCompletedLessons(start.monday, end.sunday)
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
- completedLessonsDb.deleteAll(old uniqueSubtract new)
- completedLessonsDb.insertAll(new uniqueSubtract old)
+ completedLessonsDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
},
filterResult = { it.filter { item -> item.date in start..end } }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt
index 7eb37f0b..58ce0091 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
@@ -1,16 +1,14 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.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
@@ -21,7 +19,7 @@ import javax.inject.Singleton
@Singleton
class ConferenceRepository @Inject constructor(
private val conferenceDb: ConferenceDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -46,19 +44,18 @@ class ConferenceRepository @Inject constructor(
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
},
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getConferences()
.mapToEntities(semester)
.filter { it.date >= startDate }
},
saveFetchResult = { old, new ->
- val conferencesToSave = (new uniqueSubtract old).onEach {
- if (notify) it.isNotified = false
- }
-
- conferenceDb.deleteAll(old uniqueSubtract new)
- conferenceDb.insertAll(conferencesToSave)
+ conferenceDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = (new uniqueSubtract old).onEach {
+ if (notify) it.isNotified = false
+ },
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
}
)
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 96026a55..89dbcd5c 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
@@ -1,18 +1,16 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.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
@@ -23,7 +21,7 @@ import javax.inject.Singleton
@Singleton
class ExamRepository @Inject constructor(
private val examDb: ExamDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -56,18 +54,17 @@ class ExamRepository @Inject constructor(
)
},
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getExams(start.startExamsDay, start.endExamsDay)
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
- val examsToSave = (new uniqueSubtract old).onEach {
- if (notify) it.isNotified = false
- }
-
- examDb.deleteAll(old uniqueSubtract new)
- examDb.insertAll(examsToSave)
+ examDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = (new uniqueSubtract old).onEach {
+ if (notify) it.isNotified = false
+ },
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
},
filterResult = { it.filter { item -> item.date in start..end } }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt
index b271939d..7a502787 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,5 +1,6 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
@@ -10,12 +11,8 @@ 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.ui.modules.dashboard.DashboardItem
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
@@ -32,10 +29,11 @@ class GradeRepository @Inject constructor(
private val gradeDb: GradeDao,
private val gradeSummaryDb: GradeSummaryDao,
private val gradeDescriptiveDb: GradeDescriptiveDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
private val preferencesRepository: PreferencesRepository
) {
+
private val saveFetchResultMutex = Mutex()
private fun loadGrades(semesterId: Int, studentId: Int): Flow> {
@@ -62,7 +60,7 @@ class GradeRepository @Inject constructor(
details.isEmpty() || (summaries.isEmpty() && descriptive.isEmpty()) || forceRefresh || isExpired
},
query = {
- val detailsFlow = loadGrades(semester.semesterId, semester.studentId)
+ val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
val descriptiveFlow =
gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
@@ -74,8 +72,7 @@ class GradeRepository @Inject constructor(
fetch = {
val hiddenGrades = preferencesRepository.hiddenGrades
- val (details, summary, descriptive) = sdk.init(student)
- .switchSemester(semester)
+ val (details, summary, descriptive) = wulkanowySdkFactory.create(student, semester)
.getGrades(semester.semesterId)
val censoredDetails = details.filterNot { it.entry in hiddenGrades }
@@ -100,10 +97,12 @@ class GradeRepository @Inject constructor(
new: List,
notify: Boolean
) {
- gradeDescriptiveDb.deleteAll(old uniqueSubtract new)
- gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach {
- if (notify) it.isNotified = false
- })
+ gradeDescriptiveDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = (new uniqueSubtract old).onEach {
+ if (notify) it.isNotified = false
+ },
+ )
}
private suspend fun refreshGradeDetails(
@@ -114,13 +113,16 @@ class GradeRepository @Inject constructor(
) {
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date
?: student.registrationDate.toLocalDate()
- gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
- gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
- if (it.date >= notifyBreakDate) it.apply {
- isRead = false
- if (notify) isNotified = false
- }
- })
+
+ gradeDb.removeOldAndSaveNew(
+ oldItems = oldGrades uniqueSubtract newDetails,
+ newItems = (newDetails uniqueSubtract oldGrades).onEach {
+ if (it.date >= notifyBreakDate) it.apply {
+ isRead = false
+ if (notify) isNotified = false
+ }
+ },
+ )
}
private suspend fun refreshGradeSummaries(
@@ -128,31 +130,43 @@ class GradeRepository @Inject constructor(
newSummary: List,
notify: Boolean
) {
- gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
- gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
- val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
- summary.isPredictedGradeNotified = when {
- summary.predictedGrade.isEmpty() -> true
- notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
- else -> true
- }
- summary.isFinalGradeNotified = when {
- summary.finalGrade.isEmpty() -> true
- notify && oldSummary?.finalGrade != summary.finalGrade -> false
- else -> true
- }
+ gradeSummaryDb.removeOldAndSaveNew(
+ oldItems = oldSummaries uniqueSubtract newSummary,
+ newItems = (newSummary uniqueSubtract oldSummaries).onEach { summary ->
+ getGradeSummaryWithUpdatedNotificationState(
+ summary = summary,
+ oldSummary = oldSummaries.find { it.subject == summary.subject },
+ notify = notify,
+ )
+ },
+ )
+ }
- summary.predictedGradeLastChange = when {
- oldSummary == null -> Instant.now()
- summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
- else -> oldSummary.predictedGradeLastChange
- }
- summary.finalGradeLastChange = when {
- oldSummary == null -> Instant.now()
- summary.finalGrade != oldSummary.finalGrade -> Instant.now()
- else -> oldSummary.finalGradeLastChange
- }
- })
+ private fun getGradeSummaryWithUpdatedNotificationState(
+ summary: GradeSummary,
+ oldSummary: GradeSummary?,
+ notify: Boolean,
+ ) {
+ summary.isPredictedGradeNotified = when {
+ summary.predictedGrade.isEmpty() -> true
+ notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
+ else -> true
+ }
+ summary.isFinalGradeNotified = when {
+ summary.finalGrade.isEmpty() -> true
+ notify && oldSummary?.finalGrade != summary.finalGrade -> false
+ else -> true
+ }
+ summary.predictedGradeLastChange = when {
+ oldSummary == null -> Instant.now()
+ summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
+ else -> oldSummary.predictedGradeLastChange
+ }
+ summary.finalGradeLastChange = when {
+ oldSummary == null -> Instant.now()
+ summary.finalGrade != oldSummary.finalGrade -> Instant.now()
+ else -> oldSummary.finalGradeLastChange
+ }
}
fun getUnreadGrades(semester: Semester): Flow> {
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 23d7b858..f120d34f 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
@@ -1,5 +1,6 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
@@ -12,14 +13,11 @@ import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.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.*
+import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
@@ -28,7 +26,7 @@ class GradeStatisticsRepository @Inject constructor(
private val gradePartialStatisticsDb: GradePartialStatisticsDao,
private val gradePointsStatisticsDb: GradePointsStatisticsDao,
private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -56,14 +54,15 @@ class GradeStatisticsRepository @Inject constructor(
},
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getGradesPartialStatistics(semester.semesterId)
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
- gradePartialStatisticsDb.deleteAll(old uniqueSubtract new)
- gradePartialStatisticsDb.insertAll(new uniqueSubtract old)
+ gradePartialStatisticsDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester))
},
mapResult = { items ->
@@ -80,6 +79,7 @@ class GradeStatisticsRepository @Inject constructor(
)
listOf(summaryItem) + items
}
+
else -> items.filter { it.subject == subjectName }
}.mapPartialToStatisticItems()
}
@@ -101,14 +101,15 @@ class GradeStatisticsRepository @Inject constructor(
},
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getGradesSemesterStatistics(semester.semesterId)
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
- gradeSemesterStatisticsDb.deleteAll(old uniqueSubtract new)
- gradeSemesterStatisticsDb.insertAll(new uniqueSubtract old)
+ gradeSemesterStatisticsDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester))
},
mapResult = { items ->
@@ -138,6 +139,7 @@ class GradeStatisticsRepository @Inject constructor(
}
listOf(summaryItem) + itemsWithAverage
}
+
else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems()
}
@@ -157,14 +159,15 @@ class GradeStatisticsRepository @Inject constructor(
},
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getGradesPointsStatistics(semester.semesterId)
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
- gradePointsStatisticsDb.deleteAll(old uniqueSubtract new)
- gradePointsStatisticsDb.insertAll(new uniqueSubtract old)
+ gradePointsStatisticsDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester))
},
mapResult = { items ->
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 010cf845..7893ef63 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
@@ -1,18 +1,16 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.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
@@ -22,7 +20,7 @@ import javax.inject.Singleton
@Singleton
class HomeworkRepository @Inject constructor(
private val homeworkDb: HomeworkDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -55,20 +53,19 @@ class HomeworkRepository @Inject constructor(
)
},
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getHomework(start.monday, end.sunday)
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
- val homeWorkToSave = (new uniqueSubtract old).onEach {
- if (notify) it.isNotified = false
- }
val filteredOld = old.filterNot { it.isAddedByUser }
- homeworkDb.deleteAll(filteredOld uniqueSubtract new)
- homeworkDb.insertAll(homeWorkToSave)
-
+ homeworkDb.removeOldAndSaveNew(
+ oldItems = filteredOld uniqueSubtract new,
+ newItems = (new uniqueSubtract old).onEach {
+ if (notify) it.isNotified = false
+ },
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
}
)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt
index 4ff4517d..dfafe6c8 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
@@ -1,12 +1,13 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider
+import io.github.wulkanowy.utils.AppWidgetUpdater
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex
@@ -18,7 +19,8 @@ import javax.inject.Singleton
@Singleton
class LuckyNumberRepository @Inject constructor(
private val luckyNumberDb: LuckyNumberDao,
- private val sdk: Sdk
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
+ private val appWidgetUpdater: AppWidgetUpdater,
) {
private val saveFetchResultMutex = Mutex()
@@ -27,23 +29,28 @@ class LuckyNumberRepository @Inject constructor(
student: Student,
forceRefresh: Boolean,
notify: Boolean = false,
+ isFromAppWidget: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh },
query = { luckyNumberDb.load(student.studentId, now()) },
fetch = {
- sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
+ wulkanowySdkFactory.create(student)
+ .getLuckyNumber(student.schoolShortName)
+ ?.mapToEntity(student)
},
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)
+ luckyNumberDb.removeOldAndSaveNew(
+ oldItems = listOfNotNull(oldLuckyNumber),
+ newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }),
+ )
+ if (!isFromAppWidget) {
+ appWidgetUpdater.updateAllAppWidgetsByProvider(LuckyNumberWidgetProvider::class)
+ }
}
}
)
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 6d591c5b..f91dc63e 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
@@ -4,6 +4,7 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
@@ -18,6 +19,7 @@ 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
@@ -25,16 +27,14 @@ 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
import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
-import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@@ -47,7 +47,7 @@ class MessageRepository @Inject constructor(
private val messagesDb: MessagesDao,
private val mutedMessageSendersDao: MutedMessageSendersDao,
private val messageAttachmentDao: MessageAttachmentDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
@ApplicationContext private val context: Context,
private val refreshHelper: AutoRefreshHelper,
private val sharedPrefProvider: SharedPrefProvider,
@@ -81,19 +81,26 @@ class MessageRepository @Inject constructor(
} else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id)
},
fetch = {
- sdk.init(student).getMessages(
- folder = Folder.valueOf(folder.name),
- mailboxKey = mailbox?.globalKey,
- ).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
+ wulkanowySdkFactory.create(student)
+ .getMessages(
+ folder = Folder.valueOf(folder.name),
+ mailboxKey = mailbox?.globalKey,
+ )
+ .mapToEntities(
+ student = student,
+ mailbox = mailbox,
+ allMailboxes = mailboxDao.loadAll(student.email)
+ )
},
saveFetchResult = { oldWithAuthors, new ->
val old = oldWithAuthors.map { it.message }
- messagesDb.deleteAll(old uniqueSubtract new)
- messagesDb.insertAll((new uniqueSubtract old).onEach {
- val muted = isMuted(it.correspondents)
- it.isNotified = !notify || muted
- })
-
+ messagesDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = (new uniqueSubtract old).onEach {
+ val muted = isMuted(it.correspondents)
+ it.isNotified = !notify || muted
+ },
+ )
refreshHelper.updateLastRefreshTimestamp(
getRefreshKey(messagesCacheKey, mailbox, folder)
)
@@ -113,10 +120,11 @@ class MessageRepository @Inject constructor(
},
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
fetch = {
- sdk.init(student).getMessageDetails(
- messageKey = it!!.message.messageGlobalKey,
- markAsRead = message.unread && markAsRead,
- )
+ wulkanowySdkFactory.create(student)
+ .getMessageDetails(
+ messageKey = message.messageGlobalKey,
+ markAsRead = message.unread && markAsRead,
+ )
},
saveFetchResult = { old, new ->
checkNotNull(old) { "Fetched message no longer exist!" }
@@ -155,22 +163,36 @@ 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,
- )
+ wulkanowySdkFactory.create(student)
+ .sendMessage(
+ subject = subject,
+ content = content,
+ recipients = recipients.mapFromEntities(),
+ 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) {
+ wulkanowySdkFactory.create(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 },
- removeForever = firstMessage.folderId == TRASHED.id,
- )
+ wulkanowySdkFactory.create(student)
+ .deleteMessages(
+ messages = messages.map { it.messageGlobalKey },
+ removeForever = firstMessage.folderId == TRASHED.id,
+ )
if (firstMessage.folderId != TRASHED.id) {
val deletedMessages = messages.map {
@@ -184,18 +206,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(
@@ -209,7 +237,9 @@ class MessageRepository @Inject constructor(
},
query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
fetch = {
- sdk.init(student).getMailboxes().mapToEntities(student)
+ wulkanowySdkFactory.create(student)
+ .getMailboxes()
+ .mapToEntities(student)
},
saveFetchResult = { old, new ->
mailboxDao.deleteAll(old uniqueSubtract new)
@@ -240,7 +270,7 @@ class MessageRepository @Inject constructor(
value?.let { json.encodeToString(it) }
)
- suspend fun isMuted(author: String): Boolean {
+ private suspend fun isMuted(author: String): Boolean {
return mutedMessageSendersDao.checkMute(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 412f9e7f..19466554 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
@@ -1,5 +1,6 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Semester
@@ -8,11 +9,8 @@ import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MobileDeviceToken
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.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
@@ -21,7 +19,7 @@ import javax.inject.Singleton
@Singleton
class MobileDeviceRepository @Inject constructor(
private val mobileDb: MobileDeviceDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -42,30 +40,28 @@ class MobileDeviceRepository @Inject constructor(
},
query = { mobileDb.loadAll(student.userLoginId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getRegisteredDevices()
.mapToEntities(student)
},
saveFetchResult = { old, new ->
- mobileDb.deleteAll(old uniqueSubtract new)
- mobileDb.insertAll(new uniqueSubtract old)
-
+ mobileDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
}
)
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.unregisterDevice(device.deviceId)
mobileDb.deleteAll(listOf(device))
}
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
- return sdk.init(student)
- .switchSemester(semester)
+ return wulkanowySdkFactory.create(student, 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 ac83bc22..d39bdd4b 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
@@ -1,14 +1,17 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.NoteDao
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import io.github.wulkanowy.utils.toLocalDate
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@@ -17,10 +20,11 @@ import javax.inject.Singleton
@Singleton
class NoteRepository @Inject constructor(
private val noteDb: NoteDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
private val preferencesRepository: PreferencesRepository
) {
+
private val saveFetchResultMutex = Mutex()
private val cacheKey = "note"
@@ -55,21 +59,22 @@ class NoteRepository @Inject constructor(
.selectedHiddenSettingTiles
.contains(DashboardItem.HiddenSettingTile.NOTES)
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getNotes()
.filter { !notesHidden }
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
- noteDb.deleteAll(old uniqueSubtract new)
- noteDb.insertAll((new uniqueSubtract old).onEach {
+ val notesToAdd = (new uniqueSubtract old).onEach {
if (it.date >= student.registrationDate.toLocalDate()) it.apply {
isRead = false
if (notify) isNotified = false
}
- })
-
+ }
+ noteDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = notesToAdd,
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, 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 ed3fda07..8707c54a 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
@@ -12,6 +12,7 @@ import com.fredporciuncula.flow.preferences.Serializer
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.AppTheme
+import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode
@@ -43,6 +44,27 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_attendance_present
)
+ val targetAttendanceFlow: Flow
+ get() = flowSharedPref.getInt(
+ context.getString(R.string.pref_key_attendance_target),
+ context.resources.getInteger(R.integer.pref_default_attendance_target)
+ ).asFlow()
+
+ val attendanceCalculatorSortingModeFlow: Flow
+ get() = flowSharedPref.getString(
+ context.getString(R.string.pref_key_attendance_calculator_sorting_mode),
+ context.resources.getString(R.string.pref_default_attendance_calculator_sorting_mode)
+ ).asFlow().map(AttendanceCalculatorSortingMode::getByValue)
+
+ /**
+ * Subjects are empty when they don't have any attendances (total = 0, attendances = 0, absences = 0).
+ */
+ val attendanceCalculatorShowEmptySubjects: Flow
+ get() = flowSharedPref.getBoolean(
+ context.getString(R.string.pref_key_attendance_calculator_show_empty_subjects),
+ context.resources.getBoolean(R.bool.pref_default_attendance_calculator_show_empty_subjects)
+ ).asFlow()
+
private val gradeAverageModePref: Preference
get() = getObjectFlow(
R.string.pref_key_grade_average_mode,
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt
index 79984ce6..8233d932 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt
@@ -1,12 +1,15 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.RecipientDao
-import io.github.wulkanowy.data.db.entities.*
+import io.github.wulkanowy.data.db.entities.Mailbox
+import io.github.wulkanowy.data.db.entities.MailboxType
+import io.github.wulkanowy.data.db.entities.Message
+import io.github.wulkanowy.data.db.entities.Recipient
+import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
-import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import javax.inject.Inject
import javax.inject.Singleton
@@ -14,19 +17,22 @@ import javax.inject.Singleton
@Singleton
class RecipientRepository @Inject constructor(
private val recipientDb: RecipientDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
private val cacheKey = "recipient"
suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) {
- val new = sdk.init(student).getRecipients(mailbox.globalKey)
+ val new = wulkanowySdkFactory.create(student)
+ .getRecipients(mailbox.globalKey)
.mapToEntities(mailbox.globalKey)
val old = recipientDb.loadAll(type, mailbox.globalKey)
- recipientDb.deleteAll(old uniqueSubtract new)
- recipientDb.insertAll(new uniqueSubtract old)
+ recipientDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
}
@@ -54,7 +60,7 @@ class RecipientRepository @Inject constructor(
): List {
mailbox ?: return emptyList()
- return sdk.init(student)
+ return wulkanowySdkFactory.create(student)
.getMessageReplayDetails(message.messageGlobalKey)
.sender
.let(::listOf)
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt
index 5940f477..b554bda0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt
@@ -1,17 +1,23 @@
package io.github.wulkanowy.data.repositories
-import io.github.wulkanowy.sdk.Sdk
+import io.github.wulkanowy.data.WulkanowySdkFactory
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class RecoverRepository @Inject constructor(private val sdk: Sdk) {
+class RecoverRepository @Inject constructor(
+ private val wulkanowySdkFactory: WulkanowySdkFactory
+) {
- suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair {
- return sdk.getPasswordResetCaptchaCode(host, symbol)
- }
+ suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair =
+ wulkanowySdkFactory.create()
+ .getPasswordResetCaptchaCode(host, symbol)
suspend fun sendRecoverRequest(
- url: String, symbol: String, email: String, reCaptchaResponse: String
- ): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
+ url: String,
+ symbol: String,
+ email: String,
+ reCaptchaResponse: String
+ ): String = wulkanowySdkFactory.create()
+ .sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt
index 4c42d092..6a04ce75 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt
@@ -1,14 +1,13 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
-import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
@@ -18,7 +17,7 @@ import javax.inject.Singleton
@Singleton
class SchoolAnnouncementRepository @Inject constructor(
private val schoolAnnouncementDb: SchoolAnnouncementDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -41,17 +40,18 @@ class SchoolAnnouncementRepository @Inject constructor(
schoolAnnouncementDb.loadAll(student.userLoginId)
},
fetch = {
- sdk.init(student)
- .getDirectorInformation()
- .mapToEntities(student)
+ val sdk = wulkanowySdkFactory.create(student)
+ val lastAnnouncements = sdk.getLastAnnouncements().mapToEntities(student)
+ val directorInformation = sdk.getDirectorInformation().mapToEntities(student)
+ lastAnnouncements + directorInformation
},
saveFetchResult = { old, new ->
- val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach {
- if (notify) it.isNotified = false
- }
-
- schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
- schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave)
+ schoolAnnouncementDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = (new uniqueSubtract old).onEach {
+ if (notify) it.isNotified = false
+ },
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
}
)
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 f757ef04..c48abb6f 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
@@ -1,15 +1,13 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.SchoolDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
-import io.github.wulkanowy.utils.init
-import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@@ -17,7 +15,7 @@ import javax.inject.Singleton
@Singleton
class SchoolRepository @Inject constructor(
private val schoolDb: SchoolDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -40,17 +38,16 @@ class SchoolRepository @Inject constructor(
},
query = { schoolDb.load(semester.studentId, semester.classId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getSchool()
.mapToEntity(semester)
},
saveFetchResult = { old, new ->
if (old != null && new != old) {
- with(schoolDb) {
- deleteAll(listOf(old))
- insertAll(listOf(new))
- }
+ schoolDb.removeOldAndSaveNew(
+ oldItems = listOf(old),
+ newItems = listOf(new)
+ )
} else if (old == null) {
schoolDb.insertAll(listOf(new))
}
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 216a8c11..4a16d6f1 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
@@ -1,17 +1,15 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.api.SchoolsService
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.IntegrityRequest
import io.github.wulkanowy.data.pojos.LoginEvent
-import io.github.wulkanowy.sdk.Sdk
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
@@ -23,7 +21,7 @@ import kotlin.time.Duration.Companion.seconds
class SchoolsRepository @Inject constructor(
private val integrityHelper: IntegrityHelper,
private val schoolsService: SchoolsService,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
) {
suspend fun logSchoolLogin(loginData: LoginData, students: List) {
@@ -40,10 +38,9 @@ class SchoolsRepository @Inject constructor(
private suspend fun logLogin(loginData: LoginData, student: Student, semester: Semester) {
val requestId = UUID.randomUUID().toString()
val token = integrityHelper.getIntegrityToken(requestId) ?: return
+ val updatedStudent = student.copy(password = loginData.password)
- val schoolInfo = sdk
- .init(student.copy(password = loginData.password))
- .switchSemester(semester)
+ val schoolInfo = wulkanowySdkFactory.create(updatedStudent, semester)
.getSchool()
schoolsService.logLoginEvent(
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt
index dd44df70..da21f59a 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt
@@ -1,11 +1,15 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.DispatchersProvider
+import io.github.wulkanowy.utils.getCurrentOrLast
+import io.github.wulkanowy.utils.isCurrent
+import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
@@ -14,8 +18,8 @@ import javax.inject.Singleton
@Singleton
class SemesterRepository @Inject constructor(
private val semesterDb: SemesterDao,
- private val sdk: Sdk,
- private val dispatchers: DispatchersProvider
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
+ private val dispatchers: DispatchersProvider,
) {
suspend fun getSemesters(
@@ -45,6 +49,7 @@ class SemesterRepository @Inject constructor(
0 == it.diaryId && 0 == it.kindergartenDiaryId
} == true
}
+
else -> false
}
@@ -55,12 +60,17 @@ class SemesterRepository @Inject constructor(
}
private suspend fun refreshSemesters(student: Student) {
- val new = sdk.init(student).getSemesters().mapToEntities(student.studentId)
+ val new = wulkanowySdkFactory.create(student)
+ .getSemesters()
+ .mapToEntities(student.studentId)
+
if (new.isEmpty()) return Timber.i("Empty semester list!")
val old = semesterDb.loadAll(student.studentId, student.classId)
- semesterDb.deleteAll(old.uniqueSubtract(new))
- semesterDb.insertSemesters(new.uniqueSubtract(old))
+ semesterDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
}
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
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 d6cd25c8..db4c0aeb 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
@@ -1,13 +1,11 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.StudentInfoDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
-import io.github.wulkanowy.utils.init
-import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@@ -15,7 +13,7 @@ import javax.inject.Singleton
@Singleton
class StudentInfoRepository @Inject constructor(
private val studentInfoDao: StudentInfoDao,
- private val sdk: Sdk
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
) {
private val saveFetchResultMutex = Mutex()
@@ -30,16 +28,16 @@ class StudentInfoRepository @Inject constructor(
shouldFetch = { it == null || forceRefresh },
query = { studentInfoDao.loadStudentInfo(student.studentId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
- .getStudentInfo().mapToEntity(semester)
+ wulkanowySdkFactory.create(student, semester)
+ .getStudentInfo()
+ .mapToEntity(semester)
},
saveFetchResult = { old, new ->
if (old != null && new != old) {
- with(studentInfoDao) {
- deleteAll(listOf(old))
- insertAll(listOf(new))
- }
+ studentInfoDao.removeOldAndSaveNew(
+ oldItems = listOf(old),
+ newItems = listOf(new),
+ )
} else if (old == null) {
studentInfoDao.insertAll(listOf(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 e063840c..a5fea4ec 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,11 +1,13 @@
package io.github.wulkanowy.data.repositories
import androidx.room.withTransaction
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.StudentIsAuthorized
import io.github.wulkanowy.data.db.entities.StudentName
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
@@ -14,9 +16,8 @@ import io.github.wulkanowy.data.mappers.mapToPojo
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.DispatchersProvider
-import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.getCurrentOrLast
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
@@ -26,7 +27,7 @@ class StudentRepository @Inject constructor(
private val dispatchers: DispatchersProvider,
private val studentDb: StudentDao,
private val semesterDb: SemesterDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val appDatabase: AppDatabase,
private val scrambler: Scrambler,
) {
@@ -37,7 +38,7 @@ class StudentRepository @Inject constructor(
pin: String,
symbol: String,
token: String
- ): RegisterUser = sdk
+ ): RegisterUser = wulkanowySdkFactory.create()
.getStudentsFromHebe(token, pin, symbol, "")
.mapToPojo(null)
@@ -47,7 +48,7 @@ class StudentRepository @Inject constructor(
scrapperBaseUrl: String,
domainSuffix: String,
symbol: String
- ): RegisterUser = sdk
+ ): RegisterUser = wulkanowySdkFactory.create()
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol)
.mapToPojo(password)
@@ -56,7 +57,7 @@ class StudentRepository @Inject constructor(
password: String,
scrapperBaseUrl: String,
symbol: String
- ): RegisterUser = sdk
+ ): RegisterUser = wulkanowySdkFactory.create()
.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
.mapToPojo(password)
@@ -100,6 +101,25 @@ class StudentRepository @Inject constructor(
return student
}
+ suspend fun checkCurrentStudentAuthorizationStatus() {
+ val student = getCurrentStudent()
+
+ if (!student.isAuthorized) {
+ val currentSemester = semesterDb.loadAll(
+ studentId = student.studentId,
+ classId = student.classId,
+ ).getCurrentOrLast()
+ val initializedSdk = wulkanowySdkFactory.create(student, currentSemester)
+ val isAuthorized = initializedSdk.getCurrentStudent()?.isAuthorized ?: false
+
+ if (isAuthorized) {
+ studentDb.update(StudentIsAuthorized(isAuthorized = true).apply {
+ id = student.id
+ })
+ } else throw NoAuthorizationException()
+ }
+ }
+
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
@@ -149,13 +169,11 @@ class StudentRepository @Inject constructor(
.distinctBy { it.student.studentName }.size == 1
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.authorizePermission(pesel)
suspend fun refreshStudentName(student: Student, semester: Semester) {
- val newCurrentApiStudent = sdk.init(student)
- .switchSemester(semester)
+ val newCurrentApiStudent = wulkanowySdkFactory.create(student, semester)
.getCurrentStudent() ?: return
val studentName = StudentName(
@@ -176,3 +194,6 @@ class StudentRepository @Inject constructor(
}
}
}
+
+class NoAuthorizationException : Exception()
+
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 98cb181a..573c7c14 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
@@ -1,15 +1,13 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.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
@@ -18,7 +16,7 @@ import javax.inject.Singleton
@Singleton
class SubjectRepository @Inject constructor(
private val subjectDao: SubjectDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -39,15 +37,15 @@ class SubjectRepository @Inject constructor(
},
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getSubjects()
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
- subjectDao.deleteAll(old uniqueSubtract new)
- subjectDao.insertAll(new uniqueSubtract old)
-
+ subjectDao.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
}
)
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 42698f92..a5a6e3f9 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
@@ -1,15 +1,13 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.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
@@ -18,7 +16,7 @@ import javax.inject.Singleton
@Singleton
class TeacherRepository @Inject constructor(
private val teacherDb: TeacherDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper,
) {
@@ -39,15 +37,15 @@ class TeacherRepository @Inject constructor(
},
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getTeachers()
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
- teacherDb.deleteAll(old uniqueSubtract new)
- teacherDb.insertAll(new uniqueSubtract old)
-
+ teacherDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, 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 acbd02d1..60c562e1 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
@@ -1,5 +1,6 @@
package io.github.wulkanowy.data.repositories
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
@@ -11,14 +12,13 @@ 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.ui.modules.timetablewidget.TimetableWidgetProvider
+import io.github.wulkanowy.utils.AppWidgetUpdater
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
@@ -28,14 +28,16 @@ import java.time.LocalDate
import javax.inject.Inject
import javax.inject.Singleton
+
@Singleton
class TimetableRepository @Inject constructor(
private val timetableDb: TimetableDao,
private val timetableAdditionalDb: TimetableAdditionalDao,
private val timetableHeaderDb: TimetableHeaderDao,
- private val sdk: Sdk,
+ private val wulkanowySdkFactory: WulkanowySdkFactory,
private val schedulerHelper: TimetableNotificationSchedulerHelper,
private val refreshHelper: AutoRefreshHelper,
+ private val appWidgetUpdater: AppWidgetUpdater,
) {
private val saveFetchResultMutex = Mutex()
@@ -54,7 +56,8 @@ class TimetableRepository @Inject constructor(
forceRefresh: Boolean,
refreshAdditional: Boolean = false,
notify: Boolean = false,
- timetableType: TimetableType = TimetableType.NORMAL
+ timetableType: TimetableType = TimetableType.NORMAL,
+ isFromAppWidget: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = {
@@ -74,8 +77,7 @@ class TimetableRepository @Inject constructor(
},
query = { getFullTimetableFromDatabase(student, semester, start, end) },
fetch = {
- val timetableFull = sdk.init(student)
- .switchSemester(semester)
+ val timetableFull = wulkanowySdkFactory.create(student, semester)
.getTimetable(start.monday, end.sunday)
timetableFull.mapToEntities(semester)
@@ -86,6 +88,9 @@ class TimetableRepository @Inject constructor(
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
+ if (!isFromAppWidget) {
+ appWidgetUpdater.updateAllAppWidgetsByProvider(TimetableWidgetProvider::class)
+ }
},
filterResult = { (timetable, additional, headers) ->
TimetableFull(
@@ -154,8 +159,10 @@ class TimetableRepository @Inject constructor(
new.apply { if (notify) isNotified = false }
}
- timetableDb.deleteAll(lessonsToRemove)
- timetableDb.insertAll(lessonsToAdd)
+ timetableDb.removeOldAndSaveNew(
+ oldItems = lessonsToRemove,
+ newItems = lessonsToAdd,
+ )
schedulerHelper.cancelScheduled(lessonsToRemove, student)
schedulerHelper.scheduleNotifications(lessonsToAdd, student)
@@ -166,13 +173,17 @@ class TimetableRepository @Inject constructor(
new: List
) {
val oldFiltered = old.filter { !it.isAddedByUser }
- timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new)
- timetableAdditionalDb.insertAll(new uniqueSubtract old)
+ timetableAdditionalDb.removeOldAndSaveNew(
+ oldItems = oldFiltered uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
}
private suspend fun refreshDayHeaders(old: List, new: List) {
- timetableHeaderDb.deleteAll(old uniqueSubtract new)
- timetableHeaderDb.insertAll(new uniqueSubtract old)
+ timetableHeaderDb.removeOldAndSaveNew(
+ oldItems = old uniqueSubtract new,
+ newItems = new uniqueSubtract old,
+ )
}
fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant {
diff --git a/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt
new file mode 100644
index 00000000..294abd1b
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt
@@ -0,0 +1,106 @@
+package io.github.wulkanowy.domain.attendance
+
+import io.github.wulkanowy.data.*
+import io.github.wulkanowy.data.db.entities.AttendanceSummary
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.Subject
+import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode
+import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode.*
+import io.github.wulkanowy.data.pojos.AttendanceData
+import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
+import io.github.wulkanowy.data.repositories.PreferencesRepository
+import io.github.wulkanowy.data.repositories.SubjectRepository
+import io.github.wulkanowy.utils.allAbsences
+import io.github.wulkanowy.utils.allPresences
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+import kotlin.math.ceil
+import kotlin.math.floor
+
+class GetAttendanceCalculatorDataUseCase @Inject constructor(
+ private val subjectRepository: SubjectRepository,
+ private val attendanceSummaryRepository: AttendanceSummaryRepository,
+ private val preferencesRepository: PreferencesRepository,
+) {
+
+ operator fun invoke(
+ student: Student,
+ semester: Semester,
+ forceRefresh: Boolean,
+ ): Flow>> =
+ subjectRepository.getSubjects(student, semester, forceRefresh)
+ .mapResourceData { subjects -> subjects.sortedBy(Subject::name) }
+ .combineWithResourceData(preferencesRepository.targetAttendanceFlow, ::Pair)
+ .flatMapResourceData { (subjects, targetFreq) ->
+ combineResourceFlows(subjects.map { subject ->
+ attendanceSummaryRepository.getAttendanceSummary(
+ student = student,
+ semester = semester,
+ subjectId = subject.realId,
+ forceRefresh = forceRefresh
+ ).mapResourceData { summaries ->
+ summaries.toAttendanceData(subject.name, targetFreq)
+ }
+ })
+ // Every individual combined flow causes separate network requests to update data.
+ // When there is N child flows, they can cause up to N-1 items to be emitted. Since all
+ // requests are usually completed in less than 5s, there is no need to emit multiple
+ // intermediates that will be visible for barely any time.
+ .debounceIntermediates()
+ }
+ .combineWithResourceData(preferencesRepository.attendanceCalculatorShowEmptySubjects) { attendanceDataList, showEmptySubjects ->
+ attendanceDataList.filter { it.total != 0 || showEmptySubjects }
+ }
+ .combineWithResourceData(preferencesRepository.attendanceCalculatorSortingModeFlow, List::sortedBy)
+}
+
+private fun List.toAttendanceData(subjectName: String, targetFreq: Int): AttendanceData {
+ val presences = sumOf { it.allPresences }
+ val absences = sumOf { it.allAbsences }
+ return AttendanceData(
+ subjectName = subjectName,
+ lessonBalance = calcLessonBalance(
+ targetFreq.toDouble() / 100, presences, absences
+ ),
+ presences = presences,
+ absences = absences,
+ )
+}
+
+private fun calcLessonBalance(targetFreq: Double, presences: Int, absences: Int): Int {
+ val total = presences + absences
+ // The `+ 1` is to avoid false positives in close cases. Eg.:
+ // target frequency 99%, 1 presence. Without the `+ 1` this would be reported shown as
+ // a positive balance of +1, however that is not actually true as skipping one class
+ // would make it so that the balance would actually be negative (-98). The `+ 1`
+ // fixes this and makes sure that in situations like these, it's not reporting incorrect
+ // balances
+ return when {
+ presences / (total + 1f) >= targetFreq -> calcMissingAbsences(
+ targetFreq, absences, presences
+ )
+ presences / (total + 0f) < targetFreq -> -calcMissingPresences(
+ targetFreq, absences, presences
+ )
+ else -> 0
+ }
+}
+
+private fun calcMissingPresences(targetFreq: Double, absences: Int, presences: Int) =
+ calcMinRequiredPresencesFor(targetFreq, absences) - presences
+
+private fun calcMinRequiredPresencesFor(targetFreq: Double, absences: Int) =
+ ceil((targetFreq / (1 - targetFreq)) * absences).toInt()
+
+private fun calcMissingAbsences(targetFreq: Double, absences: Int, presences: Int) =
+ calcMinRequiredAbsencesFor(targetFreq, presences) - absences
+
+private fun calcMinRequiredAbsencesFor(targetFreq: Double, presences: Int) =
+ floor((presences * (1 - targetFreq)) / targetFreq).toInt()
+
+private fun List.sortedBy(mode: AttendanceCalculatorSortingMode) = when (mode) {
+ ALPHABETIC -> sortedBy(AttendanceData::subjectName)
+ ATTENDANCE -> sortedByDescending(AttendanceData::presencePercentage)
+ LESSON_BALANCE -> sortedBy(AttendanceData::lessonBalance)
+}
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 29996db7..922c3536 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
@@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.base
import android.app.ActivityManager
+import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.Toast
@@ -17,6 +18,8 @@ import io.github.wulkanowy.utils.FragmentLifecycleLogger
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.openInternetBrowser
+import timber.log.Timber
+import java.time.Instant
import javax.inject.Inject
abstract class BaseActivity, VB : ViewBinding> :
@@ -36,16 +39,26 @@ abstract class BaseActivity, VB : ViewBinding> :
abstract var presenter: T
+ private var lastDialogOpenTime = mutableMapOf()
+
override fun onCreate(savedInstanceState: Bundle?) {
inject()
themeManager.applyActivityTheme(this)
super.onCreate(savedInstanceState)
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
+ applyCustomTaskDescription()
+ }
- @Suppress("DEPRECATION")
- setTaskDescription(
- ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface))
- )
+ @Suppress("DEPRECATION")
+ private fun applyCustomTaskDescription() {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) return
+ try {
+ val newColor = getThemeAttrColor(R.attr.colorSurface)
+ val taskDescription = ActivityManager.TaskDescription(null, null, newColor)
+ setTaskDescription(taskDescription)
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
}
override fun showError(text: String, error: Throwable) {
@@ -70,6 +83,8 @@ abstract class BaseActivity, VB : ViewBinding> :
}
override fun showExpiredCredentialsDialog() {
+ if (!shouldShowDialog(DIALOG_ERROR_BAD_CREDENTIALS)) return
+
MaterialAlertDialogBuilder(this)
.setTitle(R.string.main_expired_credentials_title)
.setMessage(R.string.main_expired_credentials_description)
@@ -83,6 +98,8 @@ abstract class BaseActivity, VB : ViewBinding> :
}
override fun showDecryptionFailedDialog() {
+ if (!shouldShowDialog(DIALOG_ERROR_DECRYPTION_FAILED)) return
+
MaterialAlertDialogBuilder(this)
.setTitle(R.string.main_session_expired)
.setMessage(R.string.main_session_relogin)
@@ -119,4 +136,21 @@ abstract class BaseActivity, VB : ViewBinding> :
protected open fun inject() {
throw UnsupportedOperationException()
}
+
+ private fun shouldShowDialog(name: String): Boolean {
+ val lastOpenTime = lastDialogOpenTime[name]
+ val now = Instant.now()
+
+ if (lastOpenTime != null && now.isBefore(lastOpenTime.plusSeconds(1))) {
+ Timber.i("Dialog $name was shown less than a second ago. Skip")
+ return false
+ }
+ lastDialogOpenTime[name] = Instant.now()
+ return true
+ }
+
+ companion object {
+ private const val DIALOG_ERROR_BAD_CREDENTIALS = "dialog_error_bad_credentials"
+ private const val DIALOG_ERROR_DECRYPTION_FAILED = "dialog_error_decryption_failed"
+ }
}
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 7109f1ff..00a2ab22 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
@@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.base
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.data.repositories.NoAuthorizationException
import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
@@ -40,7 +40,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
is ScramblerException -> onDecryptionFailed()
is BadCredentialsException -> onExpiredCredentials()
is NoCurrentStudentException -> onNoCurrentStudent()
- is AuthorizationRequiredException -> onAuthorizationRequired()
+ is NoAuthorizationException -> onAuthorizationRequired()
is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl)
}
}
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 4e9baac3..f5689ec8 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.content.res.ColorStateList
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
@@ -33,17 +34,17 @@ class AttendanceAdapter @Inject constructor() :
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
+ val context = holder.binding.root.context
val item = items[position]
with(holder.binding) {
attendanceItemNumber.text = item.number.toString()
- attendanceItemSubject.text = item.subject.ifBlank {
- root.context.getString(R.string.all_no_data)
- }
+ attendanceItemSubject.text = item.subject
+ .ifBlank { context.getString(R.string.all_no_data) }
attendanceItemDescription.setText(item.descriptionRes)
attendanceItemDescription.setTextColor(
- root.context.getThemeAttrColor(
+ context.getThemeAttrColor(
when {
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
@@ -61,13 +62,15 @@ class AttendanceAdapter @Inject constructor() :
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
- }
- ))
+ attendanceItemAlert.imageTintList = ColorStateList.valueOf(
+ 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
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 6e842b4d..07649e43 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
@@ -14,6 +14,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogExcuseBinding
import io.github.wulkanowy.databinding.FragmentAttendanceBinding
import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.attendance.calculator.AttendanceCalculatorFragment
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
@@ -134,6 +135,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == R.id.attendanceMenuSummary) presenter.onSummarySwitchSelected()
+ else if (item.itemId == R.id.attendanceMenuCalculator) presenter.onCalculatorSwitchSelected()
else false
}
@@ -253,6 +255,10 @@ class AttendanceFragment : BaseFragment(R.layout.frag
(activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance())
}
+ override fun openCalculatorView() {
+ (activity as? MainActivity)?.pushView(AttendanceCalculatorFragment.newInstance())
+ }
+
override fun startActionMode() {
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
}
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 47f106bf..586a41ad 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
@@ -1,17 +1,36 @@
package io.github.wulkanowy.ui.modules.attendance
import android.annotation.SuppressLint
-import io.github.wulkanowy.data.*
+import io.github.wulkanowy.data.Resource
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.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.onResourceLoading
+import io.github.wulkanowy.data.onResourceNotLoading
+import io.github.wulkanowy.data.onResourceSuccess
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.resourceFlow
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AnalyticsHelper
+import io.github.wulkanowy.utils.capitalise
+import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
+import io.github.wulkanowy.utils.isExcusableOrNotExcused
+import io.github.wulkanowy.utils.isHolidays
+import io.github.wulkanowy.utils.monday
+import io.github.wulkanowy.utils.nextSchoolDay
+import io.github.wulkanowy.utils.previousOrSameSchoolDay
+import io.github.wulkanowy.utils.previousSchoolDay
+import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
@@ -29,6 +48,7 @@ class AttendancePresenter @Inject constructor(
private val prefRepository: PreferencesRepository,
private val analytics: AnalyticsHelper
) : BasePresenter(errorHandler, studentRepository) {
+
private var initialDate: LocalDate? = null
private var isWeekendHasLessons: Boolean = false
@@ -195,6 +215,11 @@ class AttendancePresenter @Inject constructor(
return true
}
+ fun onCalculatorSwitchSelected(): Boolean {
+ view?.openCalculatorView()
+ return true
+ }
+
private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading attendance data started")
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 2629c217..f51ce7c7 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,6 +56,8 @@ interface AttendanceView : BaseView {
fun openSummaryView()
+ fun openCalculatorView()
+
fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String)
fun startActionMode()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt
new file mode 100644
index 00000000..4b908bba
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt
@@ -0,0 +1,67 @@
+package io.github.wulkanowy.ui.modules.attendance.calculator
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
+import io.github.wulkanowy.R
+import io.github.wulkanowy.data.pojos.AttendanceData
+import io.github.wulkanowy.databinding.ItemAttendanceCalculatorHeaderBinding
+import javax.inject.Inject
+import kotlin.math.abs
+import kotlin.math.roundToInt
+
+class AttendanceCalculatorAdapter @Inject constructor() :
+ RecyclerView.Adapter() {
+
+ var items = emptyList()
+
+ override fun getItemCount() = items.size
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
+ ItemAttendanceCalculatorHeaderBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ )
+
+ override fun onBindViewHolder(parent: ViewHolder, position: Int) {
+ val context = parent.binding.root.context
+ val item = items[position]
+
+ with(parent.binding) {
+ attendanceCalculatorPercentage.text = "${item.presencePercentage.roundToInt()}"
+
+ attendanceCalculatorSummaryBalance.text = when {
+ item.lessonBalance > 0 -> {
+ context.getString(
+ R.string.attendance_calculator_summary_balance_positive,
+ item.lessonBalance
+ )
+ }
+
+ item.lessonBalance < 0 -> {
+ context.getString(
+ R.string.attendance_calculator_summary_balance_negative,
+ abs(item.lessonBalance)
+ )
+ }
+
+ else -> context.getString(R.string.attendance_calculator_summary_balance_neutral)
+ }
+ attendanceCalculatorWarning.isVisible = item.lessonBalance < 0
+ attendanceCalculatorTitle.text = item.subjectName
+ attendanceCalculatorSummaryValues.text = if (item.total == 0) {
+ context.getString(R.string.attendance_calculator_summary_values_empty)
+ } else {
+ context.getString(
+ R.string.attendance_calculator_summary_values,
+ item.presences,
+ item.total
+ )
+ }
+ }
+ }
+
+ class ViewHolder(val binding: ItemAttendanceCalculatorHeaderBinding) :
+ RecyclerView.ViewHolder(binding.root)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt
new file mode 100644
index 00000000..2d566701
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt
@@ -0,0 +1,105 @@
+package io.github.wulkanowy.ui.modules.attendance.calculator
+
+import android.os.Bundle
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.LinearLayoutManager
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.data.pojos.AttendanceData
+import io.github.wulkanowy.databinding.FragmentAttendanceCalculatorBinding
+import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.ui.widgets.DividerItemDecoration
+import io.github.wulkanowy.utils.getThemeAttrColor
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class AttendanceCalculatorFragment :
+ BaseFragment(R.layout.fragment_attendance_calculator),
+ AttendanceCalculatorView, MainView.TitledView {
+
+ @Inject
+ lateinit var presenter: AttendanceCalculatorPresenter
+
+ @Inject
+ lateinit var attendanceCalculatorAdapter: AttendanceCalculatorAdapter
+
+ override val titleStringId get() = R.string.attendance_title
+
+ companion object {
+ fun newInstance() = AttendanceCalculatorFragment()
+ }
+
+ override val isViewEmpty get() = attendanceCalculatorAdapter.items.isEmpty()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding = FragmentAttendanceCalculatorBinding.bind(view)
+ messageContainer = binding.attendanceCalculatorRecycler
+ presenter.onAttachView(this)
+ }
+
+ override fun initView() {
+ with(binding.attendanceCalculatorRecycler) {
+ layoutManager = LinearLayoutManager(context)
+ adapter = attendanceCalculatorAdapter
+ addItemDecoration(DividerItemDecoration(context))
+ }
+
+ with(binding) {
+ attendanceCalculatorSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ attendanceCalculatorSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ attendanceCalculatorSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
+ attendanceCalculatorErrorRetry.setOnClickListener { presenter.onRetry() }
+ attendanceCalculatorErrorDetails.setOnClickListener { presenter.onDetailsClick() }
+ }
+ }
+
+ override fun updateData(data: List) {
+ with(attendanceCalculatorAdapter) {
+ items = data
+ notifyDataSetChanged()
+ }
+ }
+
+ override fun clearView() {
+ with(attendanceCalculatorAdapter) {
+ items = emptyList()
+ notifyDataSetChanged()
+ }
+ }
+
+ override fun showEmpty(show: Boolean) {
+ binding.attendanceCalculatorEmpty.isVisible = show
+ }
+
+ override fun showErrorView(show: Boolean) {
+ binding.attendanceCalculatorError.isVisible = show
+ }
+
+ override fun setErrorDetails(message: String) {
+ binding.attendanceCalculatorErrorMessage.text = message
+ }
+
+ override fun showProgress(show: Boolean) {
+ binding.attendanceCalculatorProgress.isVisible = show
+ }
+
+ override fun enableSwipe(enable: Boolean) {
+ binding.attendanceCalculatorSwipe.isEnabled = enable
+ }
+
+ override fun showContent(show: Boolean) {
+ binding.attendanceCalculatorRecycler.isVisible = show
+ }
+
+ override fun showRefresh(show: Boolean) {
+ binding.attendanceCalculatorSwipe.isRefreshing = show
+ }
+
+ override fun onDestroyView() {
+ presenter.onDetachView()
+ super.onDestroyView()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt
new file mode 100644
index 00000000..d292e565
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt
@@ -0,0 +1,84 @@
+package io.github.wulkanowy.ui.modules.attendance.calculator
+
+import io.github.wulkanowy.data.*
+import io.github.wulkanowy.data.repositories.SemesterRepository
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.domain.attendance.GetAttendanceCalculatorDataUseCase
+import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.ui.base.ErrorHandler
+import timber.log.Timber
+import javax.inject.Inject
+
+class AttendanceCalculatorPresenter @Inject constructor(
+ errorHandler: ErrorHandler,
+ studentRepository: StudentRepository,
+ private val semesterRepository: SemesterRepository,
+ private val getAttendanceCalculatorData: GetAttendanceCalculatorDataUseCase,
+) : BasePresenter(errorHandler, studentRepository) {
+
+ private lateinit var lastError: Throwable
+
+ override fun onAttachView(view: AttendanceCalculatorView) {
+ super.onAttachView(view)
+ view.initView()
+ Timber.i("Attendance calculator view was initialized")
+ errorHandler.showErrorMessage = ::showErrorViewOnError
+ loadData()
+ }
+
+ fun onSwipeRefresh() {
+ Timber.i("Force refreshing the attendance calculator")
+ loadData(forceRefresh = true)
+ }
+
+ fun onRetry() {
+ view?.run {
+ showErrorView(false)
+ showProgress(true)
+ }
+ loadData()
+ }
+
+ fun onDetailsClick() {
+ view?.showErrorDetailsDialog(lastError)
+ }
+
+ private fun loadData(forceRefresh: Boolean = false) {
+ flatResourceFlow {
+ val student = studentRepository.getCurrentStudent()
+ val semester = semesterRepository.getCurrentSemester(student)
+ getAttendanceCalculatorData(student, semester, forceRefresh)
+ }
+ .logResourceStatus("load attendance calculator")
+ .onResourceData {
+ view?.run {
+ showProgress(false)
+ showErrorView(false)
+ showContent(it.isNotEmpty())
+ showEmpty(it.isEmpty())
+ updateData(it)
+ }
+ }
+ .onResourceIntermediate { view?.showRefresh(true) }
+ .onResourceNotLoading {
+ view?.run {
+ enableSwipe(true)
+ showRefresh(false)
+ showProgress(false)
+ }
+ }
+ .onResourceError(errorHandler::dispatch)
+ .launch()
+ }
+
+ private fun showErrorViewOnError(message: String, error: Throwable) {
+ view?.run {
+ if (isViewEmpty) {
+ lastError = error
+ setErrorDetails(message)
+ showErrorView(true)
+ showEmpty(false)
+ } else showError(message, error)
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt
new file mode 100644
index 00000000..94e66121
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt
@@ -0,0 +1,29 @@
+package io.github.wulkanowy.ui.modules.attendance.calculator
+
+import io.github.wulkanowy.data.pojos.AttendanceData
+import io.github.wulkanowy.ui.base.BaseView
+
+interface AttendanceCalculatorView : BaseView {
+
+ val isViewEmpty: Boolean
+
+ fun initView()
+
+ fun showRefresh(show: Boolean)
+
+ fun showContent(show: Boolean)
+
+ fun showProgress(show: Boolean)
+
+ fun enableSwipe(enable: Boolean)
+
+ fun showEmpty(show: Boolean)
+
+ fun showErrorView(show: Boolean)
+
+ fun setErrorDetails(message: String)
+
+ fun updateData(data: List)
+
+ fun clearView()
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt
index fa29df47..0f7c4234 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt
@@ -78,4 +78,9 @@ class AuthDialog : BaseDialogFragment(), AuthView {
override fun showDescriptionWithName(name: String) {
binding.authDescription.text = getString(R.string.auth_description, name).parseAsHtml()
}
+
+ override fun onDestroyView() {
+ presenter.onDetachView()
+ super.onDestroyView()
+ }
}
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
index ed8293a9..ce2173d2 100644
--- 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
@@ -10,9 +10,10 @@ import android.webkit.WebViewClient
import androidx.core.os.bundleOf
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
+import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.databinding.DialogCaptchaBinding
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.base.BaseDialogFragment
+import io.github.wulkanowy.utils.WebkitCookieManagerProxy
import timber.log.Timber
import javax.inject.Inject
@@ -20,7 +21,10 @@ import javax.inject.Inject
class CaptchaDialog : BaseDialogFragment() {
@Inject
- lateinit var sdk: Sdk
+ lateinit var wulkanowySdkFactory: WulkanowySdkFactory
+
+ @Inject
+ lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy
private var webView: WebView? = null
@@ -55,7 +59,7 @@ class CaptchaDialog : BaseDialogFragment() {
webView = this
with(settings) {
javaScriptEnabled = true
- userAgentString = sdk.userAgent
+ userAgentString = wulkanowySdkFactory.create().userAgent
}
webViewClient = object : WebViewClient() {
@@ -80,6 +84,7 @@ class CaptchaDialog : BaseDialogFragment() {
}
override fun onDestroy() {
+ webkitCookieManagerProxy.webkitCookieManager?.flush()
webView?.destroy()
super.onDestroy()
}
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 e8a5fa25..8da59eaf 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
@@ -159,7 +159,7 @@ class GradeAverageProvider @Inject constructor(
?.updateModifiers(student, config).orEmpty()
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
- config.isOptionalArithmeticAverage
+ isOptionalArithmeticAverage = config.isOptionalArithmeticAverage,
)
} else {
secondSemesterSubject.average
@@ -173,13 +173,21 @@ class GradeAverageProvider @Inject constructor(
config: AverageCalcParams,
): Double {
return if (!isAnyVulcanAverage || config.forceAverageCalc) {
- val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
+ val isSecondSemesterHasWeightGrade = secondSemesterSubject.grades
+ .any { it.weightValue > .0 }
+ val isSecondSemesterHasArithmeticGrade = secondSemesterSubject.grades
+ .all { it.weightValue == .0 } && config.isOptionalArithmeticAverage
+ val isSecondSemesterHaveAverage =
+ isSecondSemesterHasWeightGrade || isSecondSemesterHasArithmeticGrade
+
+ val divider = if (isSecondSemesterHaveAverage) 2 else 1
val secondSemesterAverage = secondSemesterSubject.grades
.updateModifiers(student, config)
- .calcAverage(config.isOptionalArithmeticAverage)
+ .calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage)
val firstSemesterAverage = firstSemesterSubject?.grades
?.updateModifiers(student, config)
- ?.calcAverage(config.isOptionalArithmeticAverage) ?: secondSemesterAverage
+ ?.calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage)
+ ?: secondSemesterAverage
(secondSemesterAverage + firstSemesterAverage) / divider
} else {
@@ -225,7 +233,7 @@ class GradeAverageProvider @Inject constructor(
subject = summary.subject,
average = if (!isAnyAverage || params.forceAverageCalc) {
grades.updateModifiers(student, params)
- .calcAverage(params.isOptionalArithmeticAverage)
+ .calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage)
} else summary.average,
points = summary.pointsSum,
summary = summary,
@@ -286,8 +294,13 @@ class GradeAverageProvider @Inject constructor(
proposedPoints = "",
finalPoints = "",
pointsSum = "",
- average = if (calcAverage) details.updateModifiers(student, params)
- .calcAverage(params.isOptionalArithmeticAverage) else .0
+ average = when {
+ calcAverage -> details
+ .updateModifiers(student, params)
+ .calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage)
+
+ else -> .0
+ }
)
}
}
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 1ab079a3..e6de1781 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
@@ -145,7 +145,11 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
}
if (currentStudent != null) {
- luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false)
+ luckyNumberRepository.getLuckyNumber(
+ student = currentStudent,
+ forceRefresh = false,
+ isFromAppWidget = true
+ )
.toFirstResult()
.dataOrThrow
} else null
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 5469fcad..67f729af 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
@@ -73,6 +73,7 @@ class MainPresenter @Inject constructor(
syncManager.startPeriodicSyncWorker()
checkAppSupport()
+ checkCurrentStudentAuthorizationStatus()
analytics.logEvent("app_open", "destination" to initDestination.toString())
Timber.i("Main view was initialized with $initDestination")
@@ -191,4 +192,13 @@ class MainPresenter @Inject constructor(
view?.showStudentAvatar(currentStudent)
}
+
+ private fun checkCurrentStudentAuthorizationStatus() {
+ presenterScope.launch {
+ runCatching { studentRepository.checkCurrentStudentAuthorizationStatus() }
+ .onFailure { errorHandler.dispatch(it) }
+
+ Timber.i("Current student authorization status checked")
+ }
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt
index 8bd84f2b..11d3c6c1 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt
@@ -47,7 +47,6 @@ class MailboxChooserDialog : BaseDialogFragment(),
}
- @Suppress("UNCHECKED_CAST")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.onAttachView(
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 3b33bb51..8e7c7276 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,8 +44,12 @@ 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
@@ -64,6 +68,9 @@ class MessagePreviewFragment :
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)
@@ -75,10 +82,10 @@ class MessagePreviewFragment :
get() = getString(R.string.message_not_exists)
companion object {
- const val MESSAGE_ID_KEY = "message_id"
+ private const val MESSAGE_ARG_KEY = "message"
fun newInstance(message: Message) = MessagePreviewFragment().apply {
- arguments = bundleOf(MESSAGE_ID_KEY to message)
+ arguments = bundleOf(MESSAGE_ARG_KEY to message)
}
}
@@ -94,7 +101,7 @@ class MessagePreviewFragment :
messageContainer = binding.messagePreviewContainer
presenter.onAttachView(
view = this,
- message = (savedInstanceState ?: arguments)?.serializable(MESSAGE_ID_KEY),
+ message = requireArguments().serializable(MESSAGE_ARG_KEY),
)
}
@@ -111,7 +118,9 @@ 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)
@@ -124,7 +133,9 @@ 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()
@@ -152,23 +163,17 @@ 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
menuMuteButton?.isVisible = show && isReplayable
}
- override fun setDeletedOptionsLabels() {
- menuDeleteButton?.setTitle(R.string.message_delete_forever)
- }
-
- override fun setNotDeletedOptionsLabels() {
- menuDeleteButton?.setTitle(R.string.message_move_to_trash)
- }
-
override fun showErrorView(show: Boolean) {
binding.messagePreviewError.visibility = if (show) VISIBLE else GONE
}
@@ -228,11 +233,6 @@ class MessagePreviewFragment :
(activity as MainActivity).popView()
}
- override fun onSaveInstanceState(outState: Bundle) {
- outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments)
- super.onSaveInstanceState(outState)
- }
-
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
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 2eff245f..3b3b2b42 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
@@ -3,10 +3,15 @@ package io.github.wulkanowy.ui.modules.message.preview
import android.annotation.SuppressLint
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.MessageWithAttachment
import io.github.wulkanowy.data.enums.MessageFolder
+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.onResourceNotLoading
+import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
@@ -14,9 +19,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,17 +33,17 @@ class MessagePreviewPresenter @Inject constructor(
private val analytics: AnalyticsHelper
) : BasePresenter(errorHandler, studentRepository) {
- var messageWithAttachments: MessageWithAttachment? = null
+ private var messageWithAttachments: MessageWithAttachment? = null
private lateinit var lastError: Throwable
private var retryCallback: () -> Unit = {}
- fun onAttachView(view: MessagePreviewView, message: Message?) {
+ fun onAttachView(view: MessagePreviewView, message: Message) {
super.onAttachView(view)
view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError
- loadData(requireNotNull(message))
+ loadData(message)
}
private fun onMessageLoadRetry(message: Message) {
@@ -74,6 +81,7 @@ class MessagePreviewPresenter @Inject constructor(
}
}
} else {
+ delay(1.seconds)
view?.run {
showMessage(messageNotExists)
popView()
@@ -172,13 +180,51 @@ class MessagePreviewPresenter @Inject constructor(
return true
}
+ private fun restoreMessage() {
+ val message = messageWithAttachments?.message ?: return
+
+ view?.run {
+ showContent(false)
+ showProgress(true)
+ showOptions(
+ show = false,
+ isReplayable = false,
+ isRestorable = false,
+ )
+ showErrorView(false)
+ }
+ Timber.i("Restore message ${message.messageGlobalKey}")
+ presenterScope.launch {
+ runCatching {
+ val student = studentRepository.getCurrentStudent(decryptPass = true)
+ val mailbox = messageRepository.getMailboxByStudent(student)
+ messageRepository.restoreMessages(student, mailbox, listOfNotNull(message))
+ }
+ .onFailure {
+ retryCallback = { onMessageRestore() }
+ errorHandler.dispatch(it)
+ }
+ .onSuccess {
+ view?.run {
+ showMessage(restoreMessageSuccessString)
+ popView()
+ }
+ }
+ view?.showProgress(false)
+ }
+ }
+
private fun deleteMessage() {
messageWithAttachments?.message ?: return
view?.run {
showContent(false)
showProgress(true)
- showOptions(show = false, isReplayable = false)
+ showOptions(
+ show = false,
+ isReplayable = false,
+ isRestorable = false,
+ )
showErrorView(false)
}
@@ -187,8 +233,7 @@ class MessagePreviewPresenter @Inject constructor(
presenterScope.launch {
runCatching {
val student = studentRepository.getCurrentStudent(decryptPass = true)
- val mailbox = messageRepository.getMailboxByStudent(student)
- messageRepository.deleteMessage(student, mailbox, messageWithAttachments?.message!!)
+ messageRepository.deleteMessage(student, messageWithAttachments?.message!!)
}.onFailure {
retryCallback = { onMessageDelete() }
errorHandler.dispatch(it)
@@ -213,6 +258,11 @@ class MessagePreviewPresenter @Inject constructor(
}
}
+ fun onMessageRestore(): Boolean {
+ restoreMessage()
+ return true
+ }
+
fun onMessageDelete(): Boolean {
deleteMessage()
return true
@@ -222,15 +272,9 @@ class MessagePreviewPresenter @Inject constructor(
view?.apply {
showOptions(
show = messageWithAttachments?.message != null,
- isReplayable = messageWithAttachments?.message?.folderId != MessageFolder.SENT.id,
+ isReplayable = messageWithAttachments?.message?.folderId == MessageFolder.RECEIVED.id,
+ isRestorable = messageWithAttachments?.message?.folderId == MessageFolder.TRASHED.id,
)
- messageWithAttachments?.message?.let {
- when (it.folderId == MessageFolder.TRASHED.id) {
- true -> setDeletedOptionsLabels()
- false -> setNotDeletedOptionsLabels()
- }
- }
-
}
}
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 cbe1c3cb..ee0b6ce0 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
@@ -13,6 +13,8 @@ interface MessagePreviewView : BaseView {
val unmuteMessageSuccessString: String
+ val restoreMessageSuccessString: String
+
val messageNoSubjectString: String
val printHTML: String
@@ -35,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 e776e994..6155baea 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/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt
index 4364e868..12f9d323 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 f8283721..cda0b32b 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
@@ -121,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 {
@@ -134,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) }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt
index 46999599..731488a9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt
@@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.schoolannouncement
import android.view.LayoutInflater
import android.view.ViewGroup
+import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.databinding.ItemSchoolAnnouncementBinding
@@ -29,6 +30,10 @@ class SchoolAnnouncementAdapter @Inject constructor() :
schoolAnnouncementItemDate.text = item.date.toFormattedString()
schoolAnnouncementItemType.text = item.subject
schoolAnnouncementItemContent.text = item.content.parseUonetHtml()
+ with(schoolAnnouncementItemAuthor) {
+ text = item.author
+ isVisible = !item.author.isNullOrBlank()
+ }
root.setOnClickListener { onItemClickListener(item) }
}
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 3d0c8052..ba234aae 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
@@ -4,6 +4,7 @@ import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SeekBarPreference
import com.yariksoffice.lingver.Lingver
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
@@ -36,6 +37,15 @@ class AppearanceFragment : PreferenceFragmentCompat(),
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.scheme_preferences_appearance, rootKey)
+ val attendanceTargetPref =
+ findPreference(requireContext().getString(R.string.pref_key_attendance_target))!!
+ attendanceTargetPref.setOnPreferenceChangeListener { _, newValueObj ->
+ val newValue = (((newValueObj as Int).toDouble() + 2.5) / 5).toInt() * 5
+ attendanceTargetPref.value =
+ newValue.coerceIn(attendanceTargetPref.min, attendanceTargetPref.max)
+
+ false
+ }
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
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 4cfc0322..1eb33dbc 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
@@ -104,7 +104,14 @@ class TimetableWidgetFactory(
private suspend fun getLessons(
student: Student, semester: Semester, date: LocalDate
): List {
- val timetable = timetableRepository.getTimetable(student, semester, date, date, false)
+ val timetable = timetableRepository.getTimetable(
+ student = student,
+ semester = semester,
+ start = date,
+ end = date,
+ forceRefresh = false,
+ isFromAppWidget = true
+ )
val lessons = timetable.toFirstResult().dataOrThrow.lessons
return lessons.sortedBy { it.number }
}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt b/app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt
new file mode 100644
index 00000000..1b54f40c
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt
@@ -0,0 +1,34 @@
+package io.github.wulkanowy.utils
+
+import android.appwidget.AppWidgetManager
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import dagger.hilt.android.qualifiers.ApplicationContext
+import timber.log.Timber
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+class AppWidgetUpdater @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val appWidgetManager: AppWidgetManager
+) {
+
+ fun updateAllAppWidgetsByProvider(providerClass: KClass) {
+ try {
+ val ids = appWidgetManager.getAppWidgetIds(ComponentName(context, providerClass.java))
+ if (ids.isEmpty()) return
+
+ val intent = Intent(context, providerClass.java)
+ .apply {
+ action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
+ }
+
+ context.sendBroadcast(intent)
+ } catch (e: Exception) {
+ Timber.e(e, "Failed to update all widgets for provider $providerClass")
+ }
+ }
+}
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 397c9595..3cac0b48 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
@@ -10,19 +10,19 @@ import io.github.wulkanowy.sdk.scrapper.attendance.AttendanceCategory
* (https://www.vulcan.edu.pl/vulcang_files/user/AABW/AABW-PDF/uonetplus/uonetplus_Frekwencja-liczby-obecnych-nieobecnych.pdf)
*/
-private inline val AttendanceSummary.allPresences: Double
- get() = presence.toDouble() + absenceForSchoolReasons + lateness + latenessExcused
+inline val AttendanceSummary.allPresences: Int
+ get() = presence + absenceForSchoolReasons + lateness + latenessExcused
-private inline val AttendanceSummary.allAbsences: Double
- get() = absence.toDouble() + absenceExcused
+inline val AttendanceSummary.allAbsences: Int
+ get() = absence + absenceExcused
inline val Attendance.isExcusableOrNotExcused: Boolean
get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null
-fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences)
+fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences.toDouble(), allAbsences.toDouble())
fun List.calculatePercentage(): Double {
- return calculatePercentage(sumOf { it.allPresences }, sumOf { it.allAbsences })
+ return calculatePercentage(sumOf { it.allPresences.toDouble() }, sumOf { it.allAbsences.toDouble() })
}
private fun calculatePercentage(presence: Double, absence: Double): Double {
diff --git a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt
index d3c9f800..b1742b4f 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt
@@ -4,30 +4,31 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
+import androidx.core.os.BundleCompat
import java.io.Serializable
+// Even though API was introduced in 33, we use 34 as 33 is bugged in some scenarios.
+
inline fun Bundle.serializable(key: String): T = when {
- Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)!!
+ Build.VERSION.SDK_INT >= 34 -> getSerializable(key, T::class.java)!!
else -> @Suppress("DEPRECATION") getSerializable(key) as T
}
inline fun Bundle.nullableSerializable(key: String): T? = when {
- Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)
+ Build.VERSION.SDK_INT >= 34 -> getSerializable(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializable(key) as T?
}
@Suppress("UNCHECKED_CAST")
-inline fun Bundle.parcelableArray(key: String): Array? = when {
- Build.VERSION.SDK_INT >= 33 -> getParcelableArray(key, T::class.java)
- else -> @Suppress("DEPRECATION") getParcelableArray(key) as Array?
-}
+inline fun Bundle.parcelableArray(key: String): Array? =
+ BundleCompat.getParcelableArray(this, key, T::class.java) as Array?
inline fun Intent.serializable(key: String): T = when {
- Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)!!
+ Build.VERSION.SDK_INT >= 34 -> getSerializableExtra(key, T::class.java)!!
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T
}
inline fun Intent.nullableSerializable(key: String): T? = when {
- Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)
+ Build.VERSION.SDK_INT >= 34 -> getSerializableExtra(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T?
}
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 1c229051..d541c0a7 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException
import io.github.wulkanowy.sdk.scrapper.exception.VulcanException
+import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
import okhttp3.internal.http2.StreamResetException
@@ -34,6 +35,7 @@ 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 BadCredentialsException -> R.string.error_password_invalid
is AccountInactiveException -> R.string.error_account_inactive
is VulcanException -> R.string.error_unknown_uonet
is ScrapperException -> R.string.error_unknown_app
diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
deleted file mode 100644
index 9b6ca706..00000000
--- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-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
-
-fun Sdk.init(student: Student): Sdk {
- email = student.email
- password = student.password
- symbol = student.symbol
- schoolSymbol = student.schoolSymbol
- studentId = student.studentId
- classId = student.classId
- emptyCookieJarInterceptor = true
-
- if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
- mobileBaseUrl = student.mobileBaseUrl
- } else {
- scrapperBaseUrl = student.scrapperBaseUrl
- domainSuffix = student.scrapperDomainSuffix
- loginType = Sdk.ScrapperLoginType.valueOf(student.loginType)
- }
-
- mode = Sdk.Mode.valueOf(student.loginMode)
- mobileBaseUrl = student.mobileBaseUrl
- keyId = student.certificateKey
- privatePem = student.privateKey
-
- Timber.d("Sdk in ${student.loginMode} mode reinitialized")
-
- 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
index 3d41c711..4d2dde78 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt
@@ -5,17 +5,21 @@ import java.net.CookiePolicy
import java.net.CookieStore
import java.net.HttpCookie
import java.net.URI
+import javax.inject.Inject
+import javax.inject.Singleton
import android.webkit.CookieManager as WebkitCookieManager
import java.net.CookieManager as JavaCookieManager
-class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) {
+@Singleton
+class WebkitCookieManagerProxy @Inject constructor() :
+ JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) {
- private val webkitCookieManager: WebkitCookieManager? = getWebkitCookieManager()
+ val webkitCookieManager: WebkitCookieManager? = getCookieManager()
/**
* @see [https://stackoverflow.com/a/70354583/6695449]
*/
- private fun getWebkitCookieManager(): WebkitCookieManager? {
+ private fun getCookieManager(): WebkitCookieManager? {
return try {
WebkitCookieManager.getInstance()
} catch (e: AndroidRuntimeException) {
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 ef6308b6..6eafb6a9 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.4.2
+Wersja 2.5.2
-- 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
+— naprawiliśmy omyłkowe wyświetlanie ekranu z wymaganą autoryzacją numerem PESEL
+— naprawiliśmy kilka problemów ze stabilnością
+— poprawiliśmy wyświetlanie kolorów we frekwencji
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
diff --git a/app/src/main/res/drawable/ic_menu_attendance_calculator.xml b/app/src/main/res/drawable/ic_menu_attendance_calculator.xml
new file mode 100644
index 00000000..8a7d209a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_attendance_calculator.xml
@@ -0,0 +1,5 @@
+
+
+
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 00000000..a7b5ac53
--- /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 00000000..5c8544f2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_message_restore.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/dialog_captcha.xml b/app/src/main/res/layout/dialog_captcha.xml
index 539aa0cc..019d8932 100644
--- a/app/src/main/res/layout/dialog_captcha.xml
+++ b/app/src/main/res/layout/dialog_captcha.xml
@@ -7,15 +7,18 @@
tools:context=".ui.modules.captcha.CaptchaDialog">
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0" />
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/captcha_webview" />
diff --git a/app/src/main/res/layout/fragment_attendance_calculator.xml b/app/src/main/res/layout/fragment_attendance_calculator.xml
new file mode 100644
index 00000000..346c6aec
--- /dev/null
+++ b/app/src/main/res/layout/fragment_attendance_calculator.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_attendance_calculator_header.xml b/app/src/main/res/layout/item_attendance_calculator_header.xml
new file mode 100644
index 00000000..debc7997
--- /dev/null
+++ b/app/src/main/res/layout/item_attendance_calculator_header.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_school_announcement.xml b/app/src/main/res/layout/item_school_announcement.xml
index bb0cffd1..1197c66c 100644
--- a/app/src/main/res/layout/item_school_announcement.xml
+++ b/app/src/main/res/layout/item_school_announcement.xml
@@ -11,27 +11,41 @@
android:id="@+id/schoolAnnouncementItemDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginStart="15dp"
+ android:layout_marginHorizontal="15dp"
android:layout_marginTop="10dp"
- android:layout_marginEnd="10dp"
android:textColor="?android:textColorSecondary"
android:textSize="15sp"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/schoolAnnouncementItemAuthor"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/date/ddmmyy" />
+
+
@@ -40,6 +54,7 @@
android:id="@+id/schoolAnnouncementItemContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:layout_marginHorizontal="15dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="15dp"
android:ellipsize="end"
@@ -47,8 +62,8 @@
android:maxLines="2"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="@+id/schoolAnnouncementItemType"
- app:layout_constraintStart_toStartOf="@id/schoolAnnouncementItemDate"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/schoolAnnouncementItemType"
tools:text="@tools:sample/lorem/random" />
diff --git a/app/src/main/res/layout/item_timetable.xml b/app/src/main/res/layout/item_timetable.xml
index b9966c12..d1310522 100644
--- a/app/src/main/res/layout/item_timetable.xml
+++ b/app/src/main/res/layout/item_timetable.xml
@@ -24,6 +24,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0"
tools:text="5" />
@@ -179,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/pref_target_attendance.xml b/app/src/main/res/layout/pref_target_attendance.xml
new file mode 100644
index 00000000..558b0d36
--- /dev/null
+++ b/app/src/main/res/layout/pref_target_attendance.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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 3684c267..5d48313a 100644
--- a/app/src/main/res/layout/subitem_dashboard_small_grade.xml
+++ b/app/src/main/res/layout/subitem_dashboard_small_grade.xml
@@ -11,6 +11,7 @@
android:gravity="center"
android:maxLength="5"
android:minWidth="20dp"
+ android:padding="1dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
diff --git a/app/src/main/res/menu/action_menu_attendance.xml b/app/src/main/res/menu/action_menu_attendance.xml
index bb20c8ec..5c59d239 100644
--- a/app/src/main/res/menu/action_menu_attendance.xml
+++ b/app/src/main/res/menu/action_menu_attendance.xml
@@ -1,6 +1,13 @@