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..810f5e7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,6 +67,11 @@ captures/
.idea/discord.xml
.idea/migrations.xml
.idea/androidTestResultsUserPreferences.xml
+.idea/copilot
+.idea/deploymentTargetDropDown.xml
+.idea/deploymentTargetSelector.xml
+.idea/kotlinc.xml
+.idea/studiobot.xml
# Keystore files
*.jks
@@ -113,12 +118,14 @@ 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
+
+.idea/appInsightsSettings.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..17cb0aa6 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 177
+ versionName "2.7.0"
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"'
}
@@ -190,28 +186,30 @@ ext {
android_hilt = "1.2.0"
room = "2.6.1"
chucker = "4.0.0"
- mockk = "1.13.9"
- coroutines = "1.8.0"
+ mockk = "1.13.11"
+ coroutines = "1.8.1"
}
dependencies {
- implementation 'io.github.wulkanowy:sdk:2.4.2-SNAPSHOT'
+ implementation 'io.github.wulkanowy:sdk:2.7.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$coroutines"
- implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.core:core-splashscreen:1.0.1'
- implementation "androidx.activity:activity-ktx:1.8.2"
+ implementation "androidx.activity:activity-ktx:1.9.0"
implementation "androidx.appcompat:appcompat:1.6.1"
- implementation "androidx.fragment:fragment-ktx:1.6.2"
- implementation "androidx.annotation:annotation:1.7.1"
+ implementation "androidx.fragment:fragment-ktx:1.7.1"
+ implementation "androidx.annotation:annotation:1.8.0"
+ implementation "androidx.javascriptengine:javascriptengine:1.0.0-beta01"
implementation "androidx.preference:preference-ktx:1.2.1"
implementation "androidx.recyclerview:recyclerview:1.3.2"
- implementation "androidx.viewpager2:viewpager2:1.1.0-beta02"
+ implementation "androidx.viewpager2:viewpager2:1.1.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
@@ -223,7 +221,7 @@ dependencies {
implementation "androidx.work:work-runtime:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.0"
implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room"
@@ -237,7 +235,7 @@ dependencies {
implementation 'com.github.ncapdevi:FragNav:3.3.0'
implementation "com.github.YarikSOffice:lingver:1.3.0"
- implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+ implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
@@ -250,9 +248,9 @@ dependencies {
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
- implementation 'org.apache.commons:commons-text:1.11.0'
+ implementation 'org.apache.commons:commons-text:1.12.0'
- playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
+ playImplementation platform('com.google.firebase:firebase-bom:33.0.0')
playImplementation 'com.google.firebase:firebase-analytics'
playImplementation 'com.google.firebase:firebase-messaging'
playImplementation 'com.google.firebase:firebase-crashlytics:'
@@ -278,7 +276,7 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
- testImplementation 'org.robolectric:robolectric:4.11.1'
+ testImplementation 'org.robolectric:robolectric:4.12.2'
testImplementation "androidx.test:runner:1.5.2"
testImplementation "androidx.test.ext:junit:1.1.5"
testImplementation "androidx.test:core:1.5.0"
diff --git a/app/src/debug/google-services.json b/app/google-services.json
similarity index 56%
rename from app/src/debug/google-services.json
rename to app/google-services.json
index e9303986..2f71b854 100644
--- a/app/src/debug/google-services.json
+++ b/app/google-services.json
@@ -36,6 +36,37 @@
"status": 2
}
}
+ },
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:1091101852179:android:b558a25f65d088b1",
+ "android_client_info": {
+ "package_name": "io.github.wulkanowy"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": ""
+ }
+ ],
+ "services": {
+ "analytics_service": {
+ "status": 1
+ },
+ "appinvite_service": {
+ "status": 1,
+ "other_platform_oauth_client": []
+ },
+ "ads_service": {
+ "status": 2
+ }
+ }
}
],
"configuration_version": "1"
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/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json
new file mode 100644
index 00000000..9c774bb4
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json
@@ -0,0 +1,2547 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 63,
+ "identityHash": "8c04a56e74b1c4f55302f28ede94cac0",
+ "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 DEFAULT 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": "isAuthorized",
+ "columnName": "is_authorized",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isEduOne",
+ "columnName": "is_edu_one",
+ "affinity": "INTEGER",
+ "notNull": false,
+ "defaultValue": "NULL"
+ },
+ {
+ "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, '8c04a56e74b1c4f55302f28ede94cac0')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json
new file mode 100644
index 00000000..178a5eab
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json
@@ -0,0 +1,2559 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 64,
+ "identityHash": "dd5446e82ad8d0a65c545a5dbbaeb81c",
+ "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 DEFAULT 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": "isAuthorized",
+ "columnName": "is_authorized",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isEduOne",
+ "columnName": "is_edu_one",
+ "affinity": "INTEGER",
+ "notNull": false,
+ "defaultValue": "NULL"
+ },
+ {
+ "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, `points_sum_all_year` TEXT, `average` REAL NOT NULL, `average_all_year` REAL, `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": "pointsSumAllYear",
+ "columnName": "points_sum_all_year",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "average",
+ "columnName": "average",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "averageAllYear",
+ "columnName": "average_all_year",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "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": "studentId",
+ "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": "studentId",
+ "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, 'dd5446e82ad8d0a65c545a5dbbaeb81c')"
+ ]
+ }
+}
\ 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..a4257893 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="internalOnly">
+
+
@@ -42,16 +44,16 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
+ android:resizeableActivity="true"
android:supportsRtl="false"
android:theme="@style/WulkanowyTheme"
- android:resizeableActivity="true"
tools:ignore="DataExtractionRules,UnusedAttribute">
+ tools:ignore="DiscouragedApi,LockedOrientationActivity">
@@ -155,33 +157,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..a21d56cc 100644
--- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
@@ -13,22 +13,18 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
-import io.github.wulkanowy.data.api.AdminMessageService
-import io.github.wulkanowy.data.api.SchoolsService
+import io.github.wulkanowy.data.api.services.SchoolsService
+import io.github.wulkanowy.data.api.services.WulkanowyService
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(
@@ -89,7 +71,7 @@ internal class DataModule {
okHttpClient: OkHttpClient,
json: Json,
appInfo: AppInfo
- ): AdminMessageService = Retrofit.Builder()
+ ): WulkanowyService = Retrofit.Builder()
.baseUrl(appInfo.messagesBaseUrl)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
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..61eaaea1 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,18 @@
package io.github.wulkanowy.data
+import io.github.wulkanowy.data.repositories.isEndDateReached
+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 +21,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 +94,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 +120,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 +172,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 +188,100 @@ 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)
+ val updatedShouldFetch = if (isEndDateReached) false else shouldFetch(data)
+ if (updatedShouldFetch) {
+ 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..9de751c5
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt
@@ -0,0 +1,174 @@
+package io.github.wulkanowy.data
+
+import android.content.Context
+import android.os.Build
+import androidx.javascriptengine.JavaScriptSandbox
+import com.chuckerteam.chucker.api.ChuckerInterceptor
+import com.google.common.util.concurrent.ListenableFuture
+import dagger.hilt.android.qualifiers.ApplicationContext
+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.StudentIsEduOne
+import io.github.wulkanowy.data.repositories.WulkanowyRepository
+import io.github.wulkanowy.sdk.Sdk
+import io.github.wulkanowy.sdk.scrapper.EvaluateHandler
+import io.github.wulkanowy.utils.RemoteConfigHelper
+import io.github.wulkanowy.utils.WebkitCookieManagerProxy
+import kotlinx.coroutines.guava.await
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class WulkanowySdkFactory @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val chuckerInterceptor: ChuckerInterceptor,
+ private val remoteConfig: RemoteConfigHelper,
+ private val webkitCookieManagerProxy: WebkitCookieManagerProxy,
+ private val studentDb: StudentDao,
+ private val wulkanowyRepository: WulkanowyRepository,
+) {
+
+ private val eduOneMutex = Mutex()
+ private val migrationFailedStudentIds = mutableSetOf()
+ private val sandbox: ListenableFuture? =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && JavaScriptSandbox.isSupported())
+ runCatching { JavaScriptSandbox.createConnectedInstanceAsync(context) }
+ .onFailure { Timber.e(it) }
+ .getOrNull()
+ else null
+
+ private val sdk = Sdk().apply {
+ androidVersion = Build.VERSION.RELEASE
+ buildTag = Build.MODEL
+ userAgentTemplate = remoteConfig.userAgentTemplate
+ setSimpleHttpLogger { Timber.d(it) }
+ setAdditionalCookieManager(webkitCookieManagerProxy)
+
+ // for debug only
+ addInterceptor(chuckerInterceptor, network = true)
+ }
+
+ fun createBase() = sdk
+
+ suspend fun create(): Sdk {
+ val mapping = wulkanowyRepository.getMapping()
+
+ return createBase().apply {
+ if (mapping != null) {
+ endpointsMapping = mapping.endpoints
+ vTokenMapping = mapping.vTokens
+ vHeaders = mapping.vHeaders
+ responseMapping = mapping.responseMap
+ vParamsEvaluation = createIsolate()
+ }
+ }
+ }
+
+ private suspend fun createIsolate(): suspend () -> EvaluateHandler {
+ return {
+ val isolate = sandbox?.await()?.createIsolate()
+ object : EvaluateHandler {
+ override suspend fun evaluate(code: String): String? {
+ return isolate?.evaluateJavaScriptAsync(code)?.await()
+ }
+
+ override fun close() {
+ isolate?.close()
+ }
+ }
+ }
+ }
+
+ suspend fun create(student: Student, semester: Semester? = null): Sdk {
+ val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
+ return buildSdk(student, semester, overrideIsEduOne)
+ }
+
+ private suspend fun buildSdk(
+ student: Student,
+ semester: Semester?,
+ isStudentEduOne: Boolean
+ ): Sdk {
+ return create().apply {
+ email = student.email
+ password = student.password
+ symbol = student.symbol
+ schoolSymbol = student.schoolSymbol
+ studentId = student.studentId
+ classId = student.classId
+ emptyCookieJarInterceptor = true
+ isEduOne = isStudentEduOne
+
+ 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
+ }
+ }
+ }
+
+ private suspend fun checkEduOneAndMigrateIfNecessary(student: Student): Boolean {
+ if (student.isEduOne != null) return student.isEduOne
+
+ if (student.id in migrationFailedStudentIds) {
+ Timber.i("Migration eduOne: skipping because of previous failure")
+ return false
+ }
+
+ eduOneMutex.withLock {
+ if (student.id in migrationFailedStudentIds) {
+ Timber.i("Migration eduOne: skipping because of previous failure")
+ return false
+ }
+
+ val studentFromDatabase = studentDb.loadById(student.id)
+ if (studentFromDatabase?.isEduOne != null) {
+ Timber.i("Migration eduOne: already done")
+ return studentFromDatabase.isEduOne
+ }
+
+ Timber.i("Migration eduOne: flag missing. Running migration...")
+ val initializedSdk = buildSdk(
+ student = student,
+ semester = null,
+ isStudentEduOne = false, // doesn't matter
+ )
+ val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
+ .onFailure { Timber.e(it, "Migration eduOne: can't get current student") }
+ .getOrNull()
+
+ if (newCurrentStudent == null) {
+ Timber.i("Migration eduOne: failed, so skipping")
+ migrationFailedStudentIds.add(student.id)
+ return false
+ }
+
+ Timber.i("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}")
+
+ val studentIsEduOne = StudentIsEduOne(
+ id = student.id,
+ isEduOne = newCurrentStudent.isEduOne
+ )
+ studentDb.update(studentIsEduOne)
+ return newCurrentStudent.isEduOne
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/api/models/Mapping.kt b/app/src/main/java/io/github/wulkanowy/data/api/models/Mapping.kt
new file mode 100644
index 00000000..e80f7cda
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/api/models/Mapping.kt
@@ -0,0 +1,23 @@
+package io.github.wulkanowy.data.api.models
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Mapping(
+
+ @SerialName("endpoints")
+ val endpoints: Map>>,
+
+ @SerialName("vTokens")
+ val vTokens: Map>>,
+
+ @SerialName("vTokenScheme")
+ val vTokenScheme: Map> = emptyMap(),
+
+ @SerialName("vHeaders")
+ val vHeaders: Map>> = emptyMap(),
+
+ @SerialName("responseMap")
+ val responseMap: Map>>> = emptyMap(),
+)
diff --git a/app/src/main/java/io/github/wulkanowy/data/api/SchoolsService.kt b/app/src/main/java/io/github/wulkanowy/data/api/services/SchoolsService.kt
similarity index 87%
rename from app/src/main/java/io/github/wulkanowy/data/api/SchoolsService.kt
rename to app/src/main/java/io/github/wulkanowy/data/api/services/SchoolsService.kt
index a7da9b63..07fbeb89 100644
--- a/app/src/main/java/io/github/wulkanowy/data/api/SchoolsService.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/api/services/SchoolsService.kt
@@ -1,4 +1,4 @@
-package io.github.wulkanowy.data.api
+package io.github.wulkanowy.data.api.services
import io.github.wulkanowy.data.pojos.IntegrityRequest
import io.github.wulkanowy.data.pojos.LoginEvent
diff --git a/app/src/main/java/io/github/wulkanowy/data/api/AdminMessageService.kt b/app/src/main/java/io/github/wulkanowy/data/api/services/WulkanowyService.kt
similarity index 51%
rename from app/src/main/java/io/github/wulkanowy/data/api/AdminMessageService.kt
rename to app/src/main/java/io/github/wulkanowy/data/api/services/WulkanowyService.kt
index 23f5af24..e780bdd2 100644
--- a/app/src/main/java/io/github/wulkanowy/data/api/AdminMessageService.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/api/services/WulkanowyService.kt
@@ -1,12 +1,16 @@
-package io.github.wulkanowy.data.api
+package io.github.wulkanowy.data.api.services
+import io.github.wulkanowy.data.api.models.Mapping
import io.github.wulkanowy.data.db.entities.AdminMessage
import retrofit2.http.GET
import javax.inject.Singleton
@Singleton
-interface AdminMessageService {
+interface WulkanowyService {
@GET("/v1.json")
suspend fun getAdminMessages(): List
-}
\ No newline at end of file
+
+ @GET("/mapping4.json")
+ suspend fun getMapping(): Mapping
+}
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..f23c79de 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
@@ -120,6 +120,7 @@ import io.github.wulkanowy.data.db.migrations.Migration55
import io.github.wulkanowy.data.db.migrations.Migration57
import io.github.wulkanowy.data.db.migrations.Migration58
import io.github.wulkanowy.data.db.migrations.Migration6
+import io.github.wulkanowy.data.db.migrations.Migration63
import io.github.wulkanowy.data.db.migrations.Migration7
import io.github.wulkanowy.data.db.migrations.Migration8
import io.github.wulkanowy.data.db.migrations.Migration9
@@ -173,6 +174,10 @@ 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),
+ AutoMigration(from = 62, to = 63, spec = Migration63::class),
+ AutoMigration(from = 63, to = 64),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@@ -181,7 +186,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
- const val VERSION_SCHEMA = 60
+ const val VERSION_SCHEMA = 64
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@@ -308,6 +313,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/MobileDeviceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt
index 96382cc1..5ddb4dd0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt
@@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface MobileDeviceDao : BaseDao {
- @Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC")
- fun loadAll(userLoginId: Int): Flow>
+ @Query("SELECT * FROM MobileDevices WHERE user_login_id = :studentId ORDER BY date DESC")
+ fun loadAll(studentId: Int): Flow>
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt
index c32e4aba..64d49bce 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt
@@ -10,6 +10,6 @@ import javax.inject.Singleton
@Singleton
interface SchoolAnnouncementDao : BaseDao {
- @Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC")
- fun loadAll(userLoginId: Int): Flow>
+ @Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :studentId ORDER BY date DESC")
+ fun loadAll(studentId: Int): Flow>
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt
index 4d171907..bf9a34d0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt
@@ -14,6 +14,6 @@ interface SemesterDao : BaseDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertSemesters(items: List): List
- @Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId")
+ @Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId) OR (student_id = :studentId AND class_id = 0)")
suspend fun loadAll(studentId: Int, classId: Int): List
}
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..5302b320 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,8 @@ 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.StudentIsEduOne
import io.github.wulkanowy.data.db.entities.StudentName
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import javax.inject.Singleton
@@ -23,6 +25,12 @@ 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(studentIsEduOne: StudentIsEduOne)
+
@Update(entity = Student::class)
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
@@ -39,11 +47,11 @@ abstract class StudentDao {
abstract suspend fun loadAll(): List
@Transaction
- @Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id")
+ @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0)")
abstract suspend fun loadStudentsWithSemesters(): Map>
@Transaction
- @Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id WHERE Students.id = :id")
+ @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0) WHERE Students.id = :id")
abstract suspend fun loadStudentWithSemestersById(id: Long): Map>
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
index 0c8f1a5d..a8604c5c 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
@@ -4,6 +4,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import io.github.wulkanowy.data.enums.MessageType
+import io.github.wulkanowy.data.serializers.SafeMessageTypeEnumListSerializer
+import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@@ -34,6 +36,8 @@ data class AdminMessage(
val priority: String,
+ @SerialName("messageTypes")
+ @Serializable(with = SafeMessageTypeEnumListSerializer::class)
@ColumnInfo(name = "types", defaultValue = "[]")
val types: List = emptyList(),
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt
index a42832ce..f8a357a3 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt
@@ -33,7 +33,13 @@ data class GradeSummary(
@ColumnInfo(name = "points_sum")
val pointsSum: String,
- val average: Double
+ @ColumnInfo(name = "points_sum_all_year")
+ val pointsSumAllYear: String?,
+
+ val average: Double,
+
+ @ColumnInfo(name = "average_all_year")
+ val averageAllYear: Double? = null,
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt
index 89b04ccc..44e90064 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt
@@ -9,8 +9,8 @@ import java.time.Instant
@Entity(tableName = "MobileDevices")
data class MobileDevice(
- @ColumnInfo(name = "user_login_id")
- val userLoginId: Int,
+ @ColumnInfo(name = "user_login_id") // todo: change column name
+ val studentId: Int,
@ColumnInfo(name = "device_id")
val deviceId: Int,
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..814a3c8d 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
@@ -9,14 +9,16 @@ import java.time.LocalDate
@Entity(tableName = "SchoolAnnouncements")
data class SchoolAnnouncement(
- @ColumnInfo(name = "user_login_id")
- val userLoginId: Int,
+ @ColumnInfo(name = "user_login_id") // todo: change column name
+ val studentId: Int,
val date: LocalDate,
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..0300506a 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
@@ -49,6 +49,7 @@ data class Student(
@ColumnInfo(name = "student_id")
val studentId: Int,
+ @Deprecated("not available in VULCAN anymore")
@ColumnInfo(name = "user_login_id")
val userLoginId: Int,
@@ -78,6 +79,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 = "NULL")
+ val isEduOne: Boolean?,
+
) : Serializable {
@PrimaryKey(autoGenerate = true)
@@ -88,3 +96,22 @@ data class Student(
@ColumnInfo(name = "avatar_color")
var avatarColor = 0L
}
+
+@Entity
+data class StudentIsAuthorized(
+
+ @PrimaryKey
+ var id: Long,
+
+ @ColumnInfo(name = "is_authorized", defaultValue = "NULL")
+ val isAuthorized: Boolean?,
+) : Serializable
+
+@Entity
+data class StudentIsEduOne(
+ @PrimaryKey
+ var id: Long,
+
+ @ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
+ val isEduOne: Boolean?,
+) : Serializable
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt
new file mode 100644
index 00000000..f88d31fc
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt
@@ -0,0 +1,11 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.migration.AutoMigrationSpec
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+class Migration63 : AutoMigrationSpec {
+
+ override fun onPostMigrate(db: SupportSQLiteDatabase) {
+ db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 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/MessageType.kt b/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt
index 531684e4..ecd8d916 100644
--- a/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt
@@ -4,6 +4,8 @@ enum class MessageType {
GENERAL_MESSAGE,
DASHBOARD_MESSAGE,
LOGIN_MESSAGE,
+ LOGIN_STUDENT_SELECT_MESSAGE,
+ LOGIN_SYMBOL_MESSAGE,
PASS_RESET_MESSAGE,
ERROR_OVERRIDE,
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt
new file mode 100644
index 00000000..3e7cdef5
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt
@@ -0,0 +1,11 @@
+package io.github.wulkanowy.data.enums
+
+enum class ShowAdditionalLessonsMode(val value: String) {
+ NONE("none"),
+ INLINE("inline"),
+ BELOW("below");
+
+ companion object {
+ fun getByValue(value: String) = entries.find { it.value == value } ?: INLINE
+ }
+}
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..1a84a6a5 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,
+ studentId = student.studentId,
date = it.date,
subject = it.subject,
content = it.content,
+ author = null,
+ )
+}
+
+@JvmName("mapLastAnnouncementsToEntities")
+fun List.mapToEntities(student: Student) = map {
+ SchoolAnnouncement(
+ studentId = student.studentId,
+ date = it.date,
+ subject = it.subject,
+ content = it.content,
+ author = it.author,
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt
index 66e92217..57322a7a 100644
--- a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt
@@ -37,9 +37,11 @@ fun List.mapToEntities(semester: Semester) = map {
predictedGrade = it.predicted,
finalGrade = it.final,
pointsSum = it.pointsSum,
+ pointsSumAllYear = it.pointsSumAllYear,
proposedPoints = it.proposedPoints,
finalPoints = it.finalPoints,
- average = it.average
+ average = it.average,
+ averageAllYear = it.averageAllYear,
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt
index 1f4178fa..3818f01a 100644
--- a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt
@@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.Token as SdkToken
fun List.mapToEntities(student: Student) = map {
MobileDevice(
- userLoginId = student.userLoginId,
+ studentId = student.studentId,
date = it.createDate.toInstant(),
deviceId = it.id,
name = it.name
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
deleted file mode 100644
index b831ee75..00000000
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package io.github.wulkanowy.data.repositories
-
-import io.github.wulkanowy.data.Resource
-import io.github.wulkanowy.data.api.AdminMessageService
-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.sync.Mutex
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class AdminMessageRepository @Inject constructor(
- private val adminMessageService: AdminMessageService,
- private val adminMessageDao: AdminMessageDao,
-) {
-
- private val saveFetchResultMutex = Mutex()
-
- fun getAdminMessages(): Flow>> =
- networkBoundResource(
- mutex = saveFetchResultMutex,
- isResultEmpty = { false },
- query = { adminMessageDao.loadAll() },
- fetch = { adminMessageService.getAdminMessages() },
- shouldFetch = { true },
- saveFetchResult = { oldItems, newItems ->
- adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
- },
- showSavedOnLoading = false,
- )
-}
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 bbf627de..9b94cc10 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,14 +8,11 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.utils.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
@@ -28,7 +26,7 @@ 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,
) {
@@ -59,18 +57,18 @@ class AttendanceRepository @Inject constructor(
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)
.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 } }
@@ -89,8 +87,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(
@@ -98,8 +98,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 1e2ea935..e899f900 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,11 +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.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
@@ -30,7 +28,7 @@ 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,
) {
@@ -63,8 +61,7 @@ class GradeRepository @Inject constructor(
}
},
fetch = {
- val (details, summary, descriptive) = sdk.init(student)
- .switchSemester(semester)
+ val (details, summary, descriptive) = wulkanowySdkFactory.create(student, semester)
.getGrades(semester.semesterId)
Triple(
@@ -87,10 +84,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(
@@ -101,13 +100,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(
@@ -115,31 +117,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 96f04870..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
@@ -29,11 +30,9 @@ 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.sync.Mutex
@@ -48,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,
@@ -82,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)
)
@@ -114,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!" }
@@ -158,19 +165,19 @@ class MessageRepository @Inject constructor(
recipients: List,
mailbox: Mailbox,
) {
- sdk.init(student).sendMessage(
- subject = subject,
- content = content,
- recipients = recipients.mapFromEntities(),
- mailboxId = mailbox.globalKey,
- )
+ wulkanowySdkFactory.create(student)
+ .sendMessage(
+ subject = subject,
+ content = content,
+ recipients = recipients.mapFromEntities(),
+ mailboxId = mailbox.globalKey,
+ )
refreshFolders(student, mailbox, listOf(SENT))
}
suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List) {
- sdk.init(student).restoreMessages(
- messages = messages.map { it.messageGlobalKey },
- )
+ wulkanowySdkFactory.create(student)
+ .restoreMessages(messages = messages.map { it.messageGlobalKey })
refreshFolders(student, mailbox)
}
@@ -181,10 +188,11 @@ class MessageRepository @Inject constructor(
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 {
@@ -229,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)
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..1303d0e7 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,
) {
@@ -40,32 +38,30 @@ class MobileDeviceRepository @Inject constructor(
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired
},
- query = { mobileDb.loadAll(student.userLoginId) },
+ query = { mobileDb.loadAll(student.studentId) },
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 eeb1d53e..9551e01e 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,13 +1,16 @@
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.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
@@ -16,7 +19,7 @@ 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,
) {
@@ -40,20 +43,21 @@ class NoteRepository @Inject constructor(
},
query = { noteDb.loadAll(student.studentId) },
fetch = {
- sdk.init(student)
- .switchSemester(semester)
+ wulkanowySdkFactory.create(student, semester)
.getNotes()
.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 64e60a60..29e8bccd 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt
@@ -9,10 +9,13 @@ import com.fredporciuncula.flow.preferences.Preference
import com.fredporciuncula.flow.preferences.Serializer
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
+import io.github.wulkanowy.data.api.models.Mapping
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
+import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode
import io.github.wulkanowy.data.enums.TimetableGapsMode
import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
@@ -41,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,
@@ -191,6 +215,12 @@ class PreferencesRepository @Inject constructor(
)
)
+ val showAdditionalLessonsInPlan: ShowAdditionalLessonsMode
+ get() = getString(
+ R.string.pref_key_timetable_show_additional_lessons,
+ R.string.pref_default_timetable_show_additional_lessons
+ ).let { ShowAdditionalLessonsMode.getByValue(it) }
+
val gradeSortingMode: GradeSortingMode
get() = GradeSortingMode.getByValue(
getString(
@@ -346,6 +376,15 @@ class PreferencesRepository @Inject constructor(
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
+ var mapping: Mapping?
+ get() {
+ val value = sharedPref.getString("mapping", null)
+ return value?.let { json.decodeFromString(it) }
+ }
+ set(value) = sharedPref.edit(commit = true) {
+ putString("mapping", value?.let { json.encodeToString(it) })
+ }
+
init {
if (installationId.isEmpty()) {
installationId = UUID.randomUUID().toString()
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..78d95699 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,
) {
@@ -38,26 +37,27 @@ class SchoolAnnouncementRepository @Inject constructor(
it.isEmpty() || forceRefresh || isExpired
},
query = {
- schoolAnnouncementDb.loadAll(student.userLoginId)
+ schoolAnnouncementDb.loadAll(student.studentId)
},
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))
}
)
fun getSchoolAnnouncementFromDatabase(student: Student): Flow> {
- return schoolAnnouncementDb.loadAll(student.userLoginId)
+ return schoolAnnouncementDb.loadAll(student.studentId)
}
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List) =
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..25da241a 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.api.SchoolsService
+import io.github.wulkanowy.data.WulkanowySdkFactory
+import io.github.wulkanowy.data.api.services.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..92d44650 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,20 @@ class SemesterRepository @Inject constructor(
}
private suspend fun refreshSemesters(student: Student) {
- val new = sdk.init(student).getSemesters().mapToEntities(student.studentId)
- if (new.isEmpty()) return Timber.i("Empty semester list!")
+ val new = wulkanowySdkFactory.create(student)
+ .getSemesters()
+ .mapToEntities(student.studentId)
+
+ if (new.isEmpty()) {
+ Timber.i("Empty semester list from SDK!")
+ return
+ }
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..575ca89f 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,23 +1,25 @@
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
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
+import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.mappers.mapToPojo
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.DispatchersProvider
-import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.security.Scrambler
-import io.github.wulkanowy.utils.switchSemester
import kotlinx.coroutines.withContext
+import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@@ -26,7 +28,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,9 +39,10 @@ class StudentRepository @Inject constructor(
pin: String,
symbol: String,
token: String
- ): RegisterUser = sdk
+ ): RegisterUser = wulkanowySdkFactory.create()
.getStudentsFromHebe(token, pin, symbol, "")
.mapToPojo(null)
+ .also { it.logErrors() }
suspend fun getUserSubjectsFromScrapper(
email: String,
@@ -47,18 +50,20 @@ class StudentRepository @Inject constructor(
scrapperBaseUrl: String,
domainSuffix: String,
symbol: String
- ): RegisterUser = sdk
+ ): RegisterUser = wulkanowySdkFactory.create()
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol)
.mapToPojo(password)
+ .also { it.logErrors() }
suspend fun getStudentsHybrid(
email: String,
password: String,
scrapperBaseUrl: String,
symbol: String
- ): RegisterUser = sdk
+ ): RegisterUser = wulkanowySdkFactory.create()
.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
.mapToPojo(password)
+ .also { it.logErrors() }
suspend fun getSavedStudents(decryptPass: Boolean = true): List {
return studentDb.loadStudentsWithSemesters().map { (student, semesters) ->
@@ -100,6 +105,46 @@ class StudentRepository @Inject constructor(
return student
}
+ suspend fun updateCurrentStudentAuthStatus() {
+ Timber.i("Check isAuthorized: started")
+ val student = getCurrentStudent()
+ if (student.isAuthorized) {
+ Timber.i("Check isAuthorized: already authorized")
+ return
+ }
+
+ val initializedSdk = wulkanowySdkFactory.create(student)
+ val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
+ .onFailure { Timber.e(it, "Check isAuthorized: error occurred") }
+ .getOrNull()
+
+ if (newCurrentStudent == null) {
+ Timber.d("Check isAuthorized: current user is null")
+ return
+ }
+
+ val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId)
+ if (currentStudentSemesters.isEmpty()) {
+ Timber.d("Check isAuthorized: apply empty semesters workaround")
+ semesterDb.insertSemesters(
+ items = newCurrentStudent.semesters.mapToEntities(student.studentId),
+ )
+ }
+
+ if (!newCurrentStudent.isAuthorized) {
+ Timber.i("Check isAuthorized: authorization required")
+ throw NoAuthorizationException()
+ }
+
+ val studentIsAuthorized = StudentIsAuthorized(
+ id = student.id,
+ isAuthorized = true
+ )
+
+ Timber.i("Check isAuthorized: already authorized, update local status")
+ studentDb.update(studentIsAuthorized)
+ }
+
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
@@ -149,20 +194,24 @@ 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)
- .getCurrentStudent() ?: return
+ suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) {
+ val wulkanowySdk = wulkanowySdkFactory.create(student, semester)
+ val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() }
+ .onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") }
+ .getOrNull() ?: return
val studentName = StudentName(
studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}"
).apply { id = student.id }
studentDb.update(studentName)
+ semesterDb.removeOldAndSaveNew(
+ oldItems = semesterDb.loadAll(student.studentId, semester.classId),
+ newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId)
+ )
}
suspend fun deleteStudentsAssociatedWithAccount(student: Student) {
@@ -175,4 +224,18 @@ class StudentRepository @Inject constructor(
appDatabase.clearAllTables()
}
}
+
+ private fun RegisterUser.logErrors() {
+ val symbolsErrors = symbols.filter { it.error != null }
+ .map { it.error }
+ val unitsErrors = symbols.flatMap { it.schools }
+ .filter { it.error != null }
+ .map { it.error }
+
+ (symbolsErrors + unitsErrors).forEach { error ->
+ Timber.e(error, "Error occurred while fetching students")
+ }
+ }
}
+
+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/data/repositories/WulkanowyRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/WulkanowyRepository.kt
new file mode 100644
index 00000000..a25f8a0e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/WulkanowyRepository.kt
@@ -0,0 +1,69 @@
+package io.github.wulkanowy.data.repositories
+
+import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.api.models.Mapping
+import io.github.wulkanowy.data.api.services.WulkanowyService
+import io.github.wulkanowy.data.db.dao.AdminMessageDao
+import io.github.wulkanowy.data.db.entities.AdminMessage
+import io.github.wulkanowy.data.networkBoundResource
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.getRefreshKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.sync.Mutex
+import timber.log.Timber
+import java.time.LocalDate
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private val endDate = LocalDate.of(2024, 6, 25)
+val isEndDateReached = LocalDate.now() >= endDate
+
+@Singleton
+class WulkanowyRepository @Inject constructor(
+ private val wulkanowyService: WulkanowyService,
+ private val adminMessageDao: AdminMessageDao,
+ private val preferencesRepository: PreferencesRepository,
+ private val refreshHelper: AutoRefreshHelper,
+) {
+
+ private val saveFetchResultMutex = Mutex()
+ private val cacheKey = "mapping_refresh_key"
+
+ fun getAdminMessages(): Flow>> =
+ networkBoundResource(
+ mutex = saveFetchResultMutex,
+ isResultEmpty = { false },
+ query = { adminMessageDao.loadAll() },
+ fetch = { wulkanowyService.getAdminMessages() },
+ shouldFetch = { true },
+ saveFetchResult = { oldItems, newItems ->
+ adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
+ },
+ )
+ .filterNot { it is Resource.Intermediate }
+
+ suspend fun getMapping(): Mapping? {
+ var savedMapping = preferencesRepository.mapping
+
+ val isExpired = refreshHelper.shouldBeRefreshed(
+ key = getRefreshKey(cacheKey)
+ )
+
+ if (savedMapping == null || isExpired) {
+ fetchMapping()
+ savedMapping = preferencesRepository.mapping
+ }
+
+ return savedMapping
+ }
+
+ suspend fun fetchMapping() {
+ runCatching { wulkanowyService.getMapping() }
+ .onFailure { Timber.e(it) }
+ .onSuccess {
+ preferencesRepository.mapping = it
+ refreshHelper.updateLastRefreshTimestamp(cacheKey)
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt b/app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt
new file mode 100644
index 00000000..a95eab80
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt
@@ -0,0 +1,27 @@
+package io.github.wulkanowy.data.serializers
+
+import io.github.wulkanowy.data.enums.MessageType
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.builtins.ListSerializer
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+
+@OptIn(ExperimentalSerializationApi::class)
+object SafeMessageTypeEnumListSerializer : KSerializer> {
+
+ private val serializer = ListSerializer(String.serializer())
+
+ override val descriptor = serializer.descriptor
+
+ override fun serialize(encoder: Encoder, value: List) {
+ encoder.encodeNotNullMark()
+ serializer.serialize(encoder, value.map { it.name })
+ }
+
+ override fun deserialize(decoder: Decoder): List =
+ serializer.deserialize(decoder).mapNotNull { enumName ->
+ MessageType.entries.find { it.name == enumName }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt
index b55bf899..8b0d67b5 100644
--- a/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt
+++ b/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt
@@ -5,14 +5,14 @@ import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageType
import io.github.wulkanowy.data.mapResourceData
-import io.github.wulkanowy.data.repositories.AdminMessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
+import io.github.wulkanowy.data.repositories.WulkanowyRepository
import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class GetAppropriateAdminMessageUseCase @Inject constructor(
- private val adminMessageRepository: AdminMessageRepository,
+ private val wulkanowyRepository: WulkanowyRepository,
private val preferencesRepository: PreferencesRepository,
private val appInfo: AppInfo
) {
@@ -22,7 +22,7 @@ class GetAppropriateAdminMessageUseCase @Inject constructor(
}
operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow> {
- return adminMessageRepository.getAdminMessages().mapResourceData { adminMessages ->
+ return wulkanowyRepository.getAdminMessages().mapResourceData { adminMessages ->
adminMessages
.asSequence()
.filter { it.isNotDismissed() }
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/domain/messages/GetMailboxByStudentUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt
index 669514aa..8f25eba1 100644
--- a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt
+++ b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt
@@ -59,7 +59,7 @@ class GetMailboxByStudentUseCase @Inject constructor(
private fun String.getUnauthorizedVersion(): String {
return normalizeStudentName().split(" ")
.joinToString(" ") {
- it.first() + "*".repeat(it.length - 1)
+ it.firstOrNull()?.toString().orEmpty() + "*".repeat((it.length - 1).coerceAtLeast(0))
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt
index e0a136f9..aa0700b3 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt
@@ -4,19 +4,27 @@ import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O
import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.asFlow
-import androidx.work.*
import androidx.work.BackoffPolicy.EXPONENTIAL
+import androidx.work.Constraints
+import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy.KEEP
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
+import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType.CONNECTED
import androidx.work.NetworkType.UNMETERED
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY
import io.github.wulkanowy.data.repositories.PreferencesRepository
+import io.github.wulkanowy.data.repositories.isEndDateReached
import io.github.wulkanowy.services.sync.channels.Channel
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.isHolidays
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
import timber.log.Timber
import java.time.LocalDate.now
import java.util.concurrent.TimeUnit.MINUTES
@@ -34,7 +42,9 @@ class SyncManager @Inject constructor(
) {
init {
- if (now().isHolidays) stopSyncWorker()
+ if (now().isHolidays || isEndDateReached) {
+ stopSyncWorker()
+ }
if (SDK_INT >= O) {
channels.forEach { it.create() }
@@ -50,7 +60,7 @@ class SyncManager @Inject constructor(
}
fun startPeriodicSyncWorker(restart: Boolean = false) {
- if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
+ if (preferencesRepository.isServiceEnabled && !now().isHolidays && isEndDateReached) {
val serviceInterval = preferencesRepository.servicesInterval
workManager.enqueueUniquePeriodicWork(
@@ -70,6 +80,10 @@ class SyncManager @Inject constructor(
// if quiet, no notifications will be sent
fun startOneTimeSyncWorker(quiet: Boolean = false): Flow {
+ if (isEndDateReached) {
+ return flowOf(null)
+ }
+
val work = OneTimeWorkRequestBuilder()
.setInputData(
Data.Builder()
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt
index 5dddd9a7..a7639a25 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt
@@ -15,8 +15,10 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.data.repositories.isEndDateReached
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
+import io.github.wulkanowy.sdk.scrapper.exception.FeatureUnavailableException
import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.works.Work
import io.github.wulkanowy.utils.DispatchersProvider
@@ -41,13 +43,16 @@ class SyncWorker @AssistedInject constructor(
override suspend fun doWork(): Result = withContext(dispatchersProvider.io) {
Timber.i("SyncWorker is starting")
- if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure()
+ if (!studentRepository.isCurrentStudentSet() || isEndDateReached) {
+ return@withContext Result.failure()
+ }
val (student, semester) = try {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student, true)
student to semester
} catch (e: Throwable) {
+ Timber.e(e)
return@withContext getResultFromErrors(listOf(e))
}
@@ -59,7 +64,7 @@ class SyncWorker @AssistedInject constructor(
null
} catch (e: Throwable) {
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
- if (e is FeatureDisabledException || e is FeatureNotAvailableException) {
+ if (e is FeatureDisabledException || e is FeatureNotAvailableException || e is FeatureUnavailableException) {
null
} else {
Timber.e(e)
@@ -89,6 +94,7 @@ class SyncWorker @AssistedInject constructor(
.build()
)
}
+
errors.isNotEmpty() -> Result.retry()
else -> {
preferencesRepository.lasSyncDate = Instant.now()
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 82fe69cb..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,16 +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.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
@@ -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..63d1d8be
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt
@@ -0,0 +1,133 @@
+package io.github.wulkanowy.ui.modules.attendance.calculator
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+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.MainActivity
+import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.ui.modules.settings.appearance.AppearanceFragment
+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()
+
+ @Suppress("DEPRECATION")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding = FragmentAttendanceCalculatorBinding.bind(view)
+ messageContainer = binding.attendanceCalculatorRecycler
+ presenter.onAttachView(this)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.action_menu_attendance_calculator, menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return if (item.itemId == R.id.attendance_calculator_menu_settings) presenter.onSettingsSelected()
+ else false
+ }
+
+ override fun openSettingsView() {
+ (activity as? MainActivity)?.pushView(AppearanceFragment.withFocusedPreference(getString(R.string.pref_key_attendance_target)))
+ }
+
+ 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..29cb2197
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt
@@ -0,0 +1,94 @@
+package io.github.wulkanowy.ui.modules.attendance.calculator
+
+import io.github.wulkanowy.data.flatResourceFlow
+import io.github.wulkanowy.data.logResourceStatus
+import io.github.wulkanowy.data.onResourceData
+import io.github.wulkanowy.data.onResourceError
+import io.github.wulkanowy.data.onResourceIntermediate
+import io.github.wulkanowy.data.onResourceNotLoading
+import io.github.wulkanowy.data.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)
+ }
+ }
+
+ fun onSettingsSelected(): Boolean {
+ view?.openSettingsView()
+ return true
+ }
+}
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..21afe532
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt
@@ -0,0 +1,31 @@
+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()
+
+ fun openSettingsView()
+}
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/auth/AuthPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt
index 3c061f49..0be086b6 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt
@@ -5,6 +5,7 @@ import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import kotlinx.coroutines.launch
+import timber.log.Timber
import javax.inject.Inject
class AuthPresenter @Inject constructor(
@@ -26,8 +27,12 @@ class AuthPresenter @Inject constructor(
private fun loadName() {
presenterScope.launch {
- runCatching { studentRepository.getCurrentStudent(false) }
- .onSuccess { view?.showDescriptionWithName(it.studentName) }
+ runCatching {
+ studentRepository.getCurrentStudent(false)
+ .studentName
+ .replace(" ", "\u00A0")
+ }
+ .onSuccess { view?.showDescriptionWithName(it) }
.onFailure { errorHandler.dispatch(it) }
}
}
@@ -57,8 +62,9 @@ class AuthPresenter @Inject constructor(
val semester = semesterRepository.getCurrentSemester(student)
val isSuccess = studentRepository.authorizePermission(student, semester, pesel)
+ Timber.d("Auth succeed: $isSuccess")
if (isSuccess) {
- studentRepository.refreshStudentName(student, semester)
+ studentRepository.refreshStudentAfterAuthorize(student, semester)
}
isSuccess
}
@@ -68,6 +74,7 @@ class AuthPresenter @Inject constructor(
view?.showContent(true)
}
.onSuccess {
+ Timber.d("Auth fully succeed: $it")
if (it) {
view?.showSuccess(true)
view?.showContent(false)
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..922b652b 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.createBase().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/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
index b7a0796c..25774d04 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
@@ -30,6 +30,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
+import io.github.wulkanowy.ui.modules.panicmode.PanicModeFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.utils.capitalise
@@ -125,6 +126,7 @@ class DashboardFragment : BaseFragment(R.layout.fragme
mainActivity.pushView(ConferenceFragment.newInstance())
}
onAdminMessageClickListener = presenter::onAdminMessageSelected
+ onPanicButtonClickListener = presenter::onPanicButtonClicked
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
@@ -208,7 +210,11 @@ class DashboardFragment : BaseFragment(R.layout.fragme
binding = binding.dashboardErrorAdminMessage,
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
onAdminMessageClickListener = presenter::onAdminMessageSelected,
- ).bind(adminMessageItem.adminMessage)
+ onPanicButtonClickListener = presenter::onPanicButtonClicked,
+ ).bind(
+ item = adminMessageItem.adminMessage,
+ showPanicButton = true,
+ )
}
}
@@ -236,6 +242,10 @@ class DashboardFragment : BaseFragment(R.layout.fragme
requireContext().openInternetBrowser(url)
}
+ override fun openPanicWebView(url: String) {
+ (requireActivity() as MainActivity).pushView(PanicModeFragment.newInstance(url))
+ }
+
override fun onDestroyView() {
dashboardAdapter.clearTimers()
presenter.onDetachView()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
index 3fec6256..a70f82e2 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -11,6 +11,7 @@ import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.flatResourceFlow
import io.github.wulkanowy.data.mapResourceData
import io.github.wulkanowy.data.onResourceError
+import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
import io.github.wulkanowy.data.repositories.ConferenceRepository
import io.github.wulkanowy.data.repositories.ExamRepository
@@ -23,6 +24,7 @@ import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
+import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
import io.github.wulkanowy.ui.base.BasePresenter
@@ -44,6 +46,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+import okhttp3.HttpUrl.Companion.toHttpUrl
import timber.log.Timber
import java.time.Instant
import java.time.LocalDate
@@ -282,6 +285,22 @@ class DashboardPresenter @Inject constructor(
url?.let { view?.openInternetBrowser(it) }
}
+ fun onPanicButtonClicked() {
+ resourceFlow { studentRepository.getCurrentStudent() }
+ .onResourceError { errorHandler.dispatch(it) }
+ .onResourceSuccess {
+ val baseUrl = it.scrapperBaseUrl.toHttpUrl()
+ val urlToOpen = baseUrl.newBuilder()
+ .host("uonetplus${it.scrapperDomainSuffix}.${baseUrl.host}")
+ .addPathSegment(it.symbol)
+ .build()
+ .toString()
+
+ view?.openPanicWebView(urlToOpen)
+ }
+ .launch("panic_button")
+ }
+
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow {
val selectedTiles = selectedDashboardTiles
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
index 56a0a773..dd7fb962 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
@@ -31,4 +31,6 @@ interface DashboardView : BaseView {
fun openNotificationsCenterView()
fun openInternetBrowser(url: String)
+
+ fun openPanicWebView(url: String)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt
index 7c74cae8..e5197570 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt
@@ -59,6 +59,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter Unit = {}
+ var onPanicButtonClickListener: () -> Unit = {}
+
var onAdminMessageDismissClickListener: (AdminMessage) -> Unit = {}
val items = mutableListOf()
@@ -86,35 +88,46 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter AccountViewHolder(
ItemDashboardAccountBinding.inflate(inflater, parent, false)
)
+
DashboardItem.Type.HORIZONTAL_GROUP.ordinal -> HorizontalGroupViewHolder(
ItemDashboardHorizontalGroupBinding.inflate(inflater, parent, false)
)
+
DashboardItem.Type.GRADES.ordinal -> GradesViewHolder(
ItemDashboardGradesBinding.inflate(inflater, parent, false)
)
+
DashboardItem.Type.LESSONS.ordinal -> LessonsViewHolder(
ItemDashboardLessonsBinding.inflate(inflater, parent, false)
)
+
DashboardItem.Type.HOMEWORK.ordinal -> HomeworkViewHolder(
ItemDashboardHomeworkBinding.inflate(inflater, parent, false)
)
+
DashboardItem.Type.ANNOUNCEMENTS.ordinal -> AnnouncementsViewHolder(
ItemDashboardAnnouncementsBinding.inflate(inflater, parent, false)
)
+
DashboardItem.Type.EXAMS.ordinal -> ExamsViewHolder(
ItemDashboardExamsBinding.inflate(inflater, parent, false)
)
+
DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
)
+
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false),
onAdminMessageDismissClickListener = onAdminMessageDismissClickListener,
onAdminMessageClickListener = onAdminMessageClickListener,
+ onPanicButtonClickListener = onPanicButtonClickListener,
)
+
DashboardItem.Type.ADS.ordinal -> AdsViewHolder(
ItemDashboardAdsBinding.inflate(inflater, parent, false)
)
+
else -> throw IllegalArgumentException()
}
}
@@ -129,7 +142,11 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter bindAnnouncementsViewHolder(holder, position)
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
- is AdminMessageViewHolder -> holder.bind((items[position] as DashboardItem.AdminMessages).adminMessage)
+ is AdminMessageViewHolder -> holder.bind(
+ (items[position] as DashboardItem.AdminMessages).adminMessage,
+ showPanicButton = true
+ )
+
is AdsViewHolder -> bindAdsViewHolder(holder, position)
}
}
@@ -240,12 +257,15 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter {
root.context.getThemeAttrColor(R.attr.colorOnSurface)
}
+
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
root.context.getThemeAttrColor(R.attr.colorPrimary)
}
+
attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
root.context.getThemeAttrColor(R.attr.colorTimetableChange)
}
+
else -> root.context.getThemeAttrColor(R.attr.colorOnSurface)
}
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
@@ -336,24 +356,28 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter {
dateToNavigate = tomorrowDate
updateLessonView(item, tomorrowTimetable, binding)
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
}
+
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
dateToNavigate = currentDate
updateLessonView(item, emptyList(), binding, currentDayHeader)
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
}
+
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
dateToNavigate = tomorrowDate
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
}
+
else -> {
dateToNavigate = currentDate
updateLessonView(item, emptyList(), binding)
@@ -461,6 +485,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter {
firstTitleAndValueTextColor =
context.getThemeAttrColor(R.attr.colorOnSurface)
@@ -468,6 +493,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter {
firstTitleAndValueTextColor =
context.getThemeAttrColor(R.attr.colorOnSurface)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt
index 1e0f0bdb..94a7686d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt
@@ -13,9 +13,10 @@ class AdminMessageViewHolder(
private val binding: ItemDashboardAdminMessageBinding,
private val onAdminMessageDismissClickListener: (AdminMessage) -> Unit,
private val onAdminMessageClickListener: (String?) -> Unit,
+ private val onPanicButtonClickListener: () -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
- fun bind(item: AdminMessage?) {
+ fun bind(item: AdminMessage?, showPanicButton: Boolean = false) {
item ?: return
val context = binding.root.context
@@ -48,10 +49,14 @@ class AdminMessageViewHolder(
dashboardAdminMessageItemClose.setOnClickListener {
onAdminMessageDismissClickListener(item)
}
+ dashboardPanicSection.root.isVisible = showPanicButton
+ dashboardPanicSection.dashboardPanicButton.setOnClickListener {
+ onPanicButtonClickListener()
+ }
- root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
+ dashboardAdminMessage.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
item.destinationUrl?.let { url ->
- root.setOnClickListener { onAdminMessageClickListener(url) }
+ dashboardAdminMessage.setOnClickListener { onAdminMessageClickListener(url) }
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt
index c452204b..e92d1afb 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt
@@ -26,5 +26,7 @@ private fun generateSummary(subject: String, predicted: String, final: String) =
proposedPoints = "",
finalPoints = "",
pointsSum = "",
- average = .0
+ average = .0,
+ pointsSumAllYear = null,
+ averageAllYear = null,
)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt
index e2dc5cd8..9b21f08e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt
@@ -19,6 +19,6 @@ val debugSchoolAnnouncementItems = listOf(
private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement(
subject = subject,
content = content,
- userLoginId = 0,
+ studentId = 0,
date = LocalDate.now()
)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/end/EndFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/end/EndFragment.kt
new file mode 100644
index 00000000..cefbdddd
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/end/EndFragment.kt
@@ -0,0 +1,31 @@
+package io.github.wulkanowy.ui.modules.end
+
+import android.os.Bundle
+import android.text.method.LinkMovementMethod
+import android.view.View
+import androidx.activity.addCallback
+import androidx.core.text.HtmlCompat
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.databinding.FragmentEndBinding
+import io.github.wulkanowy.ui.base.BaseFragment
+
+@AndroidEntryPoint
+class EndFragment : BaseFragment(R.layout.fragment_end), EndView {
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding = FragmentEndBinding.bind(view)
+
+ requireActivity().onBackPressedDispatcher.addCallback {
+ requireActivity().finishAffinity()
+ }
+
+ binding.endClose.setOnClickListener { requireActivity().finishAffinity() }
+
+ val message = getString(R.string.end_message)
+ binding.endDescription.movementMethod = LinkMovementMethod.getInstance()
+ binding.endDescription.text =
+ HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_COMPACT)
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/end/EndView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/end/EndView.kt
new file mode 100644
index 00000000..730d570e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/end/EndView.kt
@@ -0,0 +1,5 @@
+package io.github.wulkanowy.ui.modules.end
+
+import io.github.wulkanowy.ui.base.BaseView
+
+interface EndView : BaseView
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..7f14c01f 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,
@@ -258,7 +266,9 @@ class GradeAverageProvider @Inject constructor(
proposedPoints = "",
finalPoints = "",
pointsSum = "",
- average = .0
+ pointsSumAllYear = null,
+ average = .0,
+ averageAllYear = null,
)
}
@@ -286,8 +296,15 @@ class GradeAverageProvider @Inject constructor(
proposedPoints = "",
finalPoints = "",
pointsSum = "",
- average = if (calcAverage) details.updateModifiers(student, params)
- .calcAverage(params.isOptionalArithmeticAverage) else .0
+ pointsSumAllYear = null,
+ average = when {
+ calcAverage -> details
+ .updateModifiers(student, params)
+ .calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage)
+
+ else -> .0
+ },
+ averageAllYear = null,
)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt
index 7ce07eb6..cc61dc25 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt
@@ -7,7 +7,6 @@ import android.view.MenuItem
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
-import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
@@ -31,14 +30,6 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade
@Inject
lateinit var presenter: GradePresenter
- private val pagerAdapter by lazy {
- BaseFragmentPagerAdapter(
- fragmentManager = childFragmentManager,
- pagesCount = 3,
- lifecycle = lifecycle,
- )
- }
-
private var semesterSwitchMenu: MenuItem? = null
companion object {
@@ -52,6 +43,8 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade
override val currentPageIndex get() = binding.gradeViewPager.currentItem
+ private var pagerAdapter: BaseFragmentPagerAdapter? = null
+
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -71,13 +64,26 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade
}
override fun initView() {
+ with(binding) {
+ gradeErrorRetry.setOnClickListener { presenter.onRetry() }
+ gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
+ }
+ }
+
+ override fun initTabs(pageCount: Int) {
+ pagerAdapter = BaseFragmentPagerAdapter(
+ lifecycle = lifecycle,
+ pagesCount = pageCount,
+ fragmentManager = childFragmentManager
+ )
+
with(binding.gradeViewPager) {
adapter = pagerAdapter
offscreenPageLimit = 3
setOnSelectPageListener(presenter::onPageSelected)
}
- with(pagerAdapter) {
+ with(pagerAdapter!!) {
containerId = binding.gradeViewPager.id
titleFactory = {
when (it) {
@@ -99,11 +105,6 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade
}
binding.gradeTabLayout.elevation = requireContext().dpToPx(4f)
-
- with(binding) {
- gradeErrorRetry.setOnClickListener { presenter.onRetry() }
- gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
- }
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -169,19 +170,20 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade
}
override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) {
- (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)
+ (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)
?.onParentLoadData(semesterId, forceRefresh)
}
override fun notifyChildParentReselected(index: Int) {
- (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected()
+ (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected()
}
override fun notifyChildSemesterChange(index: Int) {
- (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
+ (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
}
override fun onDestroyView() {
+ pagerAdapter = null
presenter.onDetachView()
super.onDestroyView()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt
index 0ae6521c..8a70b3c1 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt
@@ -22,11 +22,8 @@ class GradePresenter @Inject constructor(
) : BasePresenter(errorHandler, studentRepository) {
private var selectedIndex = 0
-
private var schoolYear = 0
-
- private var semesters = emptyList()
-
+ private var availableSemesters = emptyList()
private val loadedSemesterId = mutableMapOf()
private lateinit var lastError: Throwable
@@ -40,7 +37,7 @@ class GradePresenter @Inject constructor(
}
fun onCreateMenu() {
- if (semesters.isEmpty()) view?.showSemesterSwitch(false)
+ if (availableSemesters.isEmpty()) view?.showSemesterSwitch(false)
}
fun onViewReselected() {
@@ -49,8 +46,8 @@ class GradePresenter @Inject constructor(
}
fun onSemesterSwitch(): Boolean {
- if (semesters.isNotEmpty()) {
- view?.showSemesterDialog(selectedIndex - 1, semesters.take(2))
+ if (availableSemesters.isNotEmpty()) {
+ view?.showSemesterDialog(selectedIndex - 1, availableSemesters.take(2))
}
return true
}
@@ -83,7 +80,7 @@ class GradePresenter @Inject constructor(
}
fun onPageSelected(index: Int) {
- if (semesters.isNotEmpty()) loadChild(index)
+ if (availableSemesters.isNotEmpty()) loadChild(index)
}
fun onRetry() {
@@ -101,16 +98,24 @@ class GradePresenter @Inject constructor(
private fun loadData() {
resourceFlow {
val student = studentRepository.getCurrentStudent()
- semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
+ val semesters = semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
+
+ student to semesters
}
.logResourceStatus("load grade data")
- .onResourceData {
- val current = it.getCurrentOrLast()
- selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
- schoolYear = current.schoolYear
- semesters = it.filter { semester -> semester.diaryId == current.diaryId }
- view?.setCurrentSemesterName(current.semesterName, schoolYear)
+ .onResourceData { (student, semesters) ->
+ val currentSemester = semesters.getCurrentOrLast()
+ selectedIndex =
+ if (selectedIndex == 0) currentSemester.semesterName else selectedIndex
+ schoolYear = currentSemester.schoolYear
+ availableSemesters = semesters.filter { semester ->
+ semester.diaryId == currentSemester.diaryId
+ }
+
view?.run {
+ initTabs(if (student.isEduOne == true) 2 else 3)
+ setCurrentSemesterName(currentSemester.semesterName, schoolYear)
+
Timber.i("Loading grade data: Attempt load index $currentPageIndex")
loadChild(currentPageIndex)
showErrorView(false)
@@ -131,10 +136,10 @@ class GradePresenter @Inject constructor(
}
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
- Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${semesters.joinToString { it.semesterName.toString() }}")
+ Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${availableSemesters.joinToString { it.semesterName.toString() }}")
val newSelectedSemesterId = try {
- semesters.first { it.semesterName == selectedIndex }.semesterId
+ availableSemesters.first { it.semesterName == selectedIndex }.semesterId
} catch (e: NoSuchElementException) {
Timber.e(e, "Selected semester no exists")
return
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt
index 104f8505..fc06c480 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt
@@ -9,6 +9,8 @@ interface GradeView : BaseView {
fun initView()
+ fun initTabs(pageCount: Int)
+
fun showContent(show: Boolean)
fun showProgress(show: Boolean)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt
index 15b5db03..bcbd2df2 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt
@@ -96,9 +96,11 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter HeaderViewHolder(
HeaderGradeDetailsBinding.inflate(inflater, parent, false)
)
+
ViewType.ITEM.id -> ItemViewHolder(
ItemGradeDetailsBinding.inflate(inflater, parent, false)
)
+
else -> throw IllegalStateException()
}
}
@@ -110,6 +112,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter bindItemViewHolder(
holder = holder,
grade = items[position].value as Grade
@@ -133,6 +136,10 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter
) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
index d9621f51..ec5d34c5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
@@ -226,8 +226,9 @@ class GradeDetailsPresenter @Inject constructor(
GradeDetailsHeader(
subject = gradeSubject.subject,
average = gradeSubject.average,
+ averageAllYear = gradeSubject.summary.averageAllYear,
pointsSum = gradeSubject.points,
- grades = subItems
+ grades = subItems,
).apply {
newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size
}, ViewType.HEADER
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt
index 95cf97be..1cc74ef0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.grade.summary
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
+import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
@@ -65,37 +66,55 @@ class GradeSummaryAdapter @Inject constructor(
val gradeSummaries = items
.filter { it.gradeDescriptive == null }
.map { it.gradeSummary }
+ val isSecondSemester = items.any { item ->
+ item.gradeSummary.let { it.averageAllYear != null && it.averageAllYear != .0 }
+ }
val context = binding.root.context
val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) }
- val calculatedItemsCount = gradeSummaries.count { value -> value.average != 0.0 }
+ val calculatedSemesterItemsCount = gradeSummaries.count { value -> value.average != 0.0 }
+ val calculatedAnnualItemsCount =
+ gradeSummaries.count { value -> value.averageAllYear != 0.0 }
val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) }
val finalAverage = gradeSummaries.calcFinalAverage(
- preferencesRepository.gradePlusModifier,
- preferencesRepository.gradeMinusModifier
+ plusModifier = preferencesRepository.gradePlusModifier,
+ minusModifier = preferencesRepository.gradeMinusModifier,
)
- val calculatedAverage = gradeSummaries.filter { value -> value.average != 0.0 }
+ val calculatedSemesterAverage = gradeSummaries.filter { value -> value.average != 0.0 }
.map { values -> values.average }
.reversed() // fix average precision
.average()
.let { if (it.isNaN()) 0.0 else it }
+ val calculatedAnnualAverage = gradeSummaries.filter { value -> value.averageAllYear != 0.0 }
+ .mapNotNull { values -> values.averageAllYear }
+ .reversed() // fix average precision
+ .average()
+ .let { if (it.isNaN()) 0.0 else it }
with(binding) {
+ gradeSummaryScrollableHeaderCalculated.text = formatAverage(calculatedSemesterAverage)
+ gradeSummaryScrollableHeaderCalculatedAnnual.text =
+ formatAverage(calculatedAnnualAverage)
gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage)
- gradeSummaryScrollableHeaderCalculated.text = formatAverage(calculatedAverage)
- gradeSummaryScrollableHeaderFinalSubjectCount.text =
- context.getString(
- R.string.grade_summary_from_subjects,
- finalItemsCount,
- allItemsCount
- )
- gradeSummaryScrollableHeaderCalculatedSubjectCount.text = context.getString(
+ gradeSummaryScrollableHeaderFinalSubjectCount.text = context.getString(
R.string.grade_summary_from_subjects,
- calculatedItemsCount,
+ finalItemsCount,
allItemsCount
)
+ gradeSummaryScrollableHeaderCalculatedSubjectCount.text = context.getString(
+ R.string.grade_summary_from_subjects,
+ calculatedSemesterItemsCount,
+ allItemsCount
+ )
+ gradeSummaryScrollableHeaderCalculatedSubjectCountAnnual.text = context.getString(
+ R.string.grade_summary_from_subjects,
+ calculatedAnnualItemsCount,
+ allItemsCount
+ )
+ gradeSummaryScrollableHeaderCalculatedAnnualContainer.isVisible = isSecondSemester
gradeSummaryCalculatedAverageHelp.setOnClickListener { onCalculatedHelpClickListener() }
+ gradeSummaryCalculatedAverageHelpAnnual.setOnClickListener { onCalculatedHelpClickListener() }
gradeSummaryFinalAverageHelp.setOnClickListener { onFinalHelpClickListener() }
}
}
@@ -107,7 +126,12 @@ class GradeSummaryAdapter @Inject constructor(
with(binding) {
gradeSummaryItemTitle.text = gradeSummary.subject
gradeSummaryItemPoints.text = gradeSummary.pointsSum
+
gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "")
+ gradeSummaryItemAverageAllYear.text = gradeSummary.averageAllYear?.let {
+ formatAverage(it, "")
+ }
+
gradeSummaryItemPredicted.text =
"${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim()
gradeSummaryItemFinal.text =
@@ -116,6 +140,12 @@ class GradeSummaryAdapter @Inject constructor(
root.context.getString(R.string.all_no_data)
}
+ gradeSummaryItemAverageContainer.isVisible = gradeSummary.average != .0
+ gradeSummaryItemAverageDivider.isVisible = gradeSummaryItemAverageContainer.isVisible
+ gradeSummaryItemAverageAllYearContainer.isGone =
+ gradeSummary.averageAllYear == null || gradeSummary.averageAllYear == .0
+ gradeSummaryItemAverageAllYearDivider.isGone =
+ gradeSummaryItemAverageAllYearContainer.isGone
gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null
gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null
gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null
@@ -123,6 +153,7 @@ class GradeSummaryAdapter @Inject constructor(
gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null
gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null
gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank()
+ gradeSummaryItemPointsDivider.isVisible = gradeSummaryItemPointsContainer.isVisible
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt
index c51370ea..400d9f46 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt
@@ -98,7 +98,9 @@ class HomeworkAddDialog : BaseDialogFragment(), Homewo
rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear,
onDateSelected = {
date = it
- binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString())
+ if (isAdded) {
+ binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString())
+ }
}
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt
index 88f29578..0d9bb21e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt
@@ -15,6 +15,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.databinding.ActivityLoginBinding
import io.github.wulkanowy.ui.base.BaseActivity
+import io.github.wulkanowy.ui.modules.end.EndFragment
import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment
import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment
@@ -115,8 +116,14 @@ class LoginActivity : BaseActivity(), Logi
}
}
+ override fun navigateToEnd() {
+ openFragment(EndFragment(), clearBackStack = true)
+ }
+
override fun onResume() {
super.onResume()
inAppUpdateHelper.onResume()
+ presenter.updateSdkMappings()
+ presenter.checkIfEnd()
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt
index 9031cb8a..36552193 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt
@@ -1,14 +1,20 @@
package io.github.wulkanowy.ui.modules.login
import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.data.repositories.WulkanowyRepository
+import io.github.wulkanowy.data.repositories.isEndDateReached
+import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
+import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
class LoginPresenter @Inject constructor(
+ private val wulkanowyRepository: WulkanowyRepository,
errorHandler: ErrorHandler,
- studentRepository: StudentRepository
+ studentRepository: StudentRepository,
+ private val syncManager: SyncManager
) : BasePresenter(errorHandler, studentRepository) {
override fun onAttachView(view: LoginView) {
@@ -16,4 +22,18 @@ class LoginPresenter @Inject constructor(
view.initView()
Timber.i("Login view was initialized")
}
+
+ fun updateSdkMappings() {
+ presenterScope.launch {
+ runCatching { wulkanowyRepository.fetchMapping() }
+ .onFailure { Timber.e(it) }
+ }
+ }
+
+ fun checkIfEnd() {
+ if (isEndDateReached) {
+ syncManager.stopSyncWorker()
+ view?.navigateToEnd()
+ }
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt
index a0949e6d..3fe1f046 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginView.kt
@@ -5,4 +5,6 @@ import io.github.wulkanowy.ui.base.BaseView
interface LoginView : BaseView {
fun initView()
+
+ fun navigateToEnd()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
index 1c492069..2d1a4824 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
@@ -238,6 +238,7 @@ class LoginFormFragment : BaseFragment(R.layout.fragme
binding = binding.loginFormMessage,
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
onAdminMessageClickListener = presenter::onAdminMessageSelected,
+ onPanicButtonClickListener = {},
).bind(message)
binding.loginFormMessage.root.isVisible = message != null
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt
index e6d13182..ef8cf4ee 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt
@@ -19,19 +19,23 @@ class LoginStudentSelectAdapter @Inject constructor() :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
- return when (LoginStudentSelectItemType.values()[viewType]) {
+ return when (LoginStudentSelectItemType.entries[viewType]) {
LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder(
ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false),
)
+
LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder(
ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false)
)
+
LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder(
ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false)
)
+
LoginStudentSelectItemType.STUDENT -> StudentViewHolder(
ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false)
)
+
LoginStudentSelectItemType.HELP -> HelpViewHolder(
ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false)
)
@@ -98,9 +102,11 @@ class LoginStudentSelectAdapter @Inject constructor() :
with(binding) {
loginStudentSelectHeaderSchoolName.text = buildString {
append(item.unit.schoolName.trim())
- append(" (")
- append(item.unit.schoolShortName)
- append(")")
+ if (item.unit.schoolShortName.isNotBlank()) {
+ append(" (")
+ append(item.unit.schoolShortName)
+ append(")")
+ }
}
loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty()
loginStudentSelectHeaderSchoolError.text = item.unit.error?.message
@@ -170,9 +176,11 @@ class LoginStudentSelectAdapter @Inject constructor() :
oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> {
oldItem.symbol == newItem.symbol
}
+
oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> {
oldItem.student == newItem.student
}
+
else -> oldItem == newItem
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
index 0fe36aa9..586c395c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
@@ -6,10 +6,12 @@ import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
@@ -111,6 +113,20 @@ class LoginStudentSelectFragment :
LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog")
}
+ override fun showAdminMessage(adminMessage: AdminMessage?) {
+ AdminMessageViewHolder(
+ binding = binding.loginStudentSelectAdminMessage,
+ onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
+ onAdminMessageClickListener = presenter::onAdminMessageSelected,
+ onPanicButtonClickListener = {},
+ ).bind(adminMessage)
+ binding.loginStudentSelectAdminMessage.root.isVisible = adminMessage != null
+ }
+
+ override fun openInternetBrowser(url: String) {
+ requireContext().openInternetBrowser(url)
+ }
+
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt
index 34441418..39070cf0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt
@@ -2,16 +2,24 @@ package io.github.wulkanowy.ui.modules.login.studentselect
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.dataOrNull
+import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.enums.MessageType
+import io.github.wulkanowy.data.flatResourceFlow
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.mappers.mapToStudentWithSemesters
+import io.github.wulkanowy.data.onResourceData
+import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.pojos.RegisterStudent
import io.github.wulkanowy.data.pojos.RegisterSymbol
import io.github.wulkanowy.data.pojos.RegisterUnit
import io.github.wulkanowy.data.pojos.RegisterUser
+import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SchoolsRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
+import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
+import io.github.wulkanowy.sdk.scrapper.exception.StudentGraduateException
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter
@@ -32,6 +40,8 @@ class LoginStudentSelectPresenter @Inject constructor(
private val syncManager: SyncManager,
private val analytics: AnalyticsHelper,
private val appInfo: AppInfo,
+ private val preferencesRepository: PreferencesRepository,
+ private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase
) : BasePresenter(loginErrorHandler, studentRepository) {
private var lastError: Throwable? = null
@@ -64,6 +74,7 @@ class LoginStudentSelectPresenter @Inject constructor(
this.loginData = loginData
this.registerUser = registerUser
loadData()
+ loadAdminMessage()
}
private fun loadData() {
@@ -87,7 +98,20 @@ class LoginStudentSelectPresenter @Inject constructor(
refreshItems()
}
}
- }.launch()
+ }.launch("load_data")
+ }
+
+ private fun loadAdminMessage() {
+ flatResourceFlow {
+ getAppropriateAdminMessageUseCase(
+ scrapperBaseUrl = registerUser.scrapperBaseUrl.orEmpty(),
+ type = MessageType.LOGIN_STUDENT_SELECT_MESSAGE,
+ )
+ }
+ .logResourceStatus("load login admin message")
+ .onResourceData { view?.showAdminMessage(it) }
+ .onResourceError { view?.showAdminMessage(null) }
+ .launch("load_admin_message")
}
private fun getStudentsWithCurrentlyActiveSemesters(): List {
@@ -108,8 +132,8 @@ class LoginStudentSelectPresenter @Inject constructor(
}
private fun createItems(): List = buildList {
- val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
- val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
+ val notEmptySymbols = registerUser.symbols.filter { it.shouldShowOnTop() }
+ val emptySymbols = registerUser.symbols.filter { !it.shouldShowOnTop() }
if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) {
add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol }))
@@ -127,6 +151,10 @@ class LoginStudentSelectPresenter @Inject constructor(
add(helpItem)
}
+ private fun RegisterSymbol.shouldShowOnTop(): Boolean {
+ return schools.isNotEmpty() || error is StudentGraduateException
+ }
+
private fun createNotEmptySymbolItems(
notEmptySymbols: List,
students: List,
@@ -336,4 +364,14 @@ class LoginStudentSelectPresenter @Inject constructor(
)
}
}
+
+ fun onAdminMessageSelected(url: String?) {
+ url?.let { view?.openInternetBrowser(it) }
+ }
+
+ fun onAdminMessageDismissed(adminMessage: AdminMessage) {
+ preferencesRepository.dismissedAdminMessageIds += adminMessage.id
+
+ view?.showAdminMessage(null)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt
index b69700f1..4d0ef9e9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt
@@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.login.studentselect
+import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
@@ -25,4 +26,8 @@ interface LoginStudentSelectView : BaseView {
fun openDiscordInvite()
fun openEmail(supportInfo: LoginSupportInfo)
+
+ fun showAdminMessage(adminMessage: AdminMessage?)
+
+ fun openInternetBrowser(url: String)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt
index 23ebffe9..824d8d22 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt
@@ -9,13 +9,16 @@ import android.view.inputmethod.EditorInfo.IME_NULL
import android.widget.ArrayAdapter
import androidx.core.os.bundleOf
import androidx.core.text.parseAsHtml
+import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
@@ -179,4 +182,18 @@ class LoginSymbolFragment :
override fun openSupportDialog(supportInfo: LoginSupportInfo) {
LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog")
}
+
+ override fun showAdminMessage(adminMessage: AdminMessage?) {
+ AdminMessageViewHolder(
+ binding = binding.loginSymbolAdminMessage,
+ onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
+ onAdminMessageClickListener = presenter::onAdminMessageSelected,
+ onPanicButtonClickListener = {},
+ ).bind(adminMessage)
+ binding.loginSymbolAdminMessage.root.isVisible = adminMessage != null
+ }
+
+ override fun openInternetBrowser(url: String) {
+ requireContext().openInternetBrowser(url)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
index 5c31f14d..8de2994a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
@@ -2,10 +2,18 @@ package io.github.wulkanowy.ui.modules.login.symbol
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.dataOrNull
+import io.github.wulkanowy.data.db.entities.AdminMessage
+import io.github.wulkanowy.data.enums.MessageType
+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.pojos.RegisterUser
+import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
+import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
import io.github.wulkanowy.ui.base.BasePresenter
@@ -21,7 +29,9 @@ import javax.inject.Inject
class LoginSymbolPresenter @Inject constructor(
studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler,
- private val analytics: AnalyticsHelper
+ private val analytics: AnalyticsHelper,
+ private val preferencesRepository: PreferencesRepository,
+ private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase,
) : BasePresenter(loginErrorHandler, studentRepository) {
private var lastError: Throwable? = null
@@ -43,6 +53,21 @@ class LoginSymbolPresenter @Inject constructor(
clearAndFocusSymbol()
showSoftKeyboard()
}
+
+ loadAdminMessage()
+ }
+
+ private fun loadAdminMessage() {
+ flatResourceFlow {
+ getAppropriateAdminMessageUseCase(
+ scrapperBaseUrl = loginData.baseUrl,
+ type = MessageType.LOGIN_SYMBOL_MESSAGE,
+ )
+ }
+ .logResourceStatus("load login admin message")
+ .onResourceData { view?.showAdminMessage(it) }
+ .onResourceError { view?.showAdminMessage(null) }
+ .launch("load_admin_message")
}
fun onSymbolTextChanged() {
@@ -166,4 +191,14 @@ class LoginSymbolPresenter @Inject constructor(
)
)
}
+
+ fun onAdminMessageSelected(url: String?) {
+ url?.let { view?.openInternetBrowser(it) }
+ }
+
+ fun onAdminMessageDismissed(adminMessage: AdminMessage) {
+ preferencesRepository.dismissedAdminMessageIds += adminMessage.id
+
+ view?.showAdminMessage(null)
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt
index ace12f78..2fc91024 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt
@@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.login.symbol
+import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData
@@ -44,4 +45,8 @@ interface LoginSymbolView : BaseView {
fun openFaqPage()
fun openSupportDialog(supportInfo: LoginSupportInfo)
+
+ fun showAdminMessage(adminMessage: AdminMessage?)
+
+ fun openInternetBrowser(url: String)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt
index 0c1b89c8..9c718af4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt
@@ -33,4 +33,4 @@ class LuckyNumberHistoryAdapter @Inject constructor() :
}
class ItemViewHolder(val binding: ItemLuckyNumberHistoryBinding) : RecyclerView.ViewHolder(binding.root)
-}
\ No newline at end of file
+}
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..18437ef7 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
@@ -14,7 +14,9 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.dataOrThrow
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
+import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.data.repositories.isEndDateReached
import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity
@@ -35,6 +37,9 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
@Inject
lateinit var sharedPref: SharedPrefProvider
+ @Inject
+ lateinit var preferencesRepository: PreferencesRepository
+
companion object {
private const val LUCKY_NUMBER_WIDGET_MAX_SIZE = 196
@@ -130,6 +135,10 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
}
private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking {
+ if (isEndDateReached) {
+ return@runBlocking null
+ }
+
try {
val students = studentRepository.getSavedStudents()
val student = students.singleOrNull { it.student.id == studentId }?.student
@@ -145,7 +154,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/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
index 62c16257..23aa51b4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
@@ -33,6 +33,7 @@ import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
+import io.github.wulkanowy.ui.modules.end.EndFragment
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
@@ -138,6 +139,8 @@ class MainActivity : BaseActivity(), MainVie
override fun onResume() {
super.onResume()
inAppUpdateHelper.onResume()
+ presenter.updateSdkMappings()
+ presenter.checkIfEnd()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -361,4 +364,10 @@ class MainActivity : BaseActivity(), MainVie
super.onSaveInstanceState(outState)
navController.onSaveInstanceState(outState)
}
+
+ override fun navigateToEnd() {
+ binding.mainToolbar.isVisible = false
+ pushView(EndFragment())
+ onBackCallback?.isEnabled = false
+ }
}
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..13edac6e 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
@@ -6,6 +6,8 @@ import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.data.repositories.WulkanowyRepository
+import io.github.wulkanowy.data.repositories.isEndDateReached
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter
@@ -14,6 +16,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.AccountView
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsView
+import io.github.wulkanowy.ui.modules.end.EndView
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.AnalyticsHelper
@@ -29,6 +32,7 @@ class MainPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val preferencesRepository: PreferencesRepository,
+ private val wulkanowyRepository: WulkanowyRepository,
private val syncManager: SyncManager,
private val analytics: AnalyticsHelper,
private val json: Json,
@@ -73,6 +77,7 @@ class MainPresenter @Inject constructor(
syncManager.startPeriodicSyncWorker()
checkAppSupport()
+ updateCurrentStudentAuthStatus()
analytics.logEvent("app_open", "destination" to initDestination.toString())
Timber.i("Main view was initialized with $initDestination")
@@ -107,6 +112,7 @@ class MainPresenter @Inject constructor(
}
private fun shouldShowBottomNavigation(destination: BaseView) = when (destination) {
+ is EndView,
is AccountView,
is StudentInfoView,
is AccountDetailsView -> false
@@ -191,4 +197,25 @@ class MainPresenter @Inject constructor(
view?.showStudentAvatar(currentStudent)
}
+
+ private fun updateCurrentStudentAuthStatus() {
+ presenterScope.launch {
+ runCatching { studentRepository.updateCurrentStudentAuthStatus() }
+ .onFailure { errorHandler.dispatch(it) }
+ }
+ }
+
+ fun updateSdkMappings() {
+ presenterScope.launch {
+ runCatching { wulkanowyRepository.fetchMapping() }
+ .onFailure { Timber.e(it) }
+ }
+ }
+
+ fun checkIfEnd() {
+ if (isEndDateReached) {
+ syncManager.stopSyncWorker()
+ view?.navigateToEnd()
+ }
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
index 70a94fc8..c83d9568 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
@@ -48,6 +48,8 @@ interface MainView : BaseView {
fun openMoreDestination(destination: Destination)
+ fun navigateToEnd()
+
interface MainChildView {
fun onFragmentReselected()
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 75778bac..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
@@ -82,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)
}
}
@@ -101,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),
)
}
@@ -233,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 9bb0d32a..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
@@ -28,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) {
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 12f9d323..b29c2cd8 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
@@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
+import io.github.wulkanowy.ui.modules.panicmode.PanicModeFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor
@@ -132,6 +133,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag
)
messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
+ messageTabPanicSection.dashboardPanicButton.setOnClickListener { presenter.onPanicButtonClicked() }
}
setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle ->
@@ -283,6 +285,10 @@ class MessageTabFragment : BaseFragment(R.layout.frag
)
}
+ override fun openPanicWebView(url: String) {
+ (requireActivity() as MainActivity).pushView(PanicModeFragment.newInstance(url))
+ }
+
override fun hideKeyboard() {
activity?.hideSoftInput()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
index cda0b32b..ad3e7c9c 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
@@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.xdrop.fuzzywuzzy.FuzzySearch
+import okhttp3.HttpUrl.Companion.toHttpUrl
import timber.log.Timber
import javax.inject.Inject
import kotlin.math.pow
@@ -429,4 +430,20 @@ class MessageTabPresenter @Inject constructor(
+ dateRatio.toDouble().pow(2) * 2
).toInt()
}
+
+ fun onPanicButtonClicked() {
+ resourceFlow { studentRepository.getCurrentStudent() }
+ .onResourceError { errorHandler.dispatch(it) }
+ .onResourceSuccess {
+ val baseUrl = it.scrapperBaseUrl.toHttpUrl()
+ val urlToOpen = baseUrl.newBuilder()
+ .host("uonetplus${it.scrapperDomainSuffix}-wiadomosciplus.${baseUrl.host}")
+ .addPathSegment(it.symbol)
+ .build()
+ .toString()
+
+ view?.openPanicWebView(urlToOpen)
+ }
+ .launch("panic_button")
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt
index 247af434..e544a3d6 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt
@@ -50,4 +50,6 @@ interface MessageTabView : BaseView {
fun showRecyclerBottomPadding(show: Boolean)
fun showMailboxChooser(mailboxes: List)
+
+ fun openPanicWebView(url: String)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/panicmode/PanicModeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/panicmode/PanicModeFragment.kt
new file mode 100644
index 00000000..df51c5f9
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/panicmode/PanicModeFragment.kt
@@ -0,0 +1,99 @@
+package io.github.wulkanowy.ui.modules.panicmode
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.View
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.activity.addCallback
+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.FragmentPanicModeBinding
+import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.utils.WebkitCookieManagerProxy
+import io.github.wulkanowy.utils.openInternetBrowser
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class PanicModeFragment : BaseFragment(R.layout.fragment_panic_mode),
+ MainView.TitledView {
+
+ @Inject
+ lateinit var wulkanowySdkFactory: WulkanowySdkFactory
+
+ @Inject
+ lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy
+
+ private var webView: WebView? = null
+
+ override val titleStringId: Int get() = R.string.panic_mode_title
+
+ companion object {
+
+ private const val PANIC_URL = "panic_mode_url"
+ fun newInstance(url: String?): PanicModeFragment {
+ return PanicModeFragment().apply {
+ arguments = bundleOf(PANIC_URL to url)
+ }
+ }
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding = FragmentPanicModeBinding.bind(view)
+
+ binding.panicModeRefresh.setOnClickListener {
+ binding.panicModeWebview.loadUrl(
+ binding.panicModeWebview.url ?: arguments?.getString(PANIC_URL).orEmpty()
+ )
+ }
+ binding.panicModeBack.setOnClickListener { binding.panicModeWebview.goBack() }
+ binding.panicModeHome.setOnClickListener {
+ binding.panicModeWebview.loadUrl(
+ arguments?.getString(PANIC_URL).orEmpty()
+ )
+ }
+ binding.panicModeForward.setOnClickListener { binding.panicModeWebview.goForward() }
+ binding.panicModeShare.setOnClickListener {
+ requireContext().openInternetBrowser(
+ binding.panicModeWebview.url.toString(),
+ )
+ }
+
+ val onBackPressedCallback = requireActivity().onBackPressedDispatcher
+ .addCallback(viewLifecycleOwner) {
+ binding.panicModeWebview.goBack()
+ }
+
+ with(binding.panicModeWebview) {
+ webView = this
+ with(settings) {
+ javaScriptEnabled = true
+ userAgentString = wulkanowySdkFactory.createBase().userAgent
+ }
+
+ webViewClient = object : WebViewClient() {
+ override fun doUpdateVisitedHistory(
+ view: WebView?,
+ url: String?,
+ isReload: Boolean
+ ) {
+ binding.panicModeBack.isEnabled = binding.panicModeWebview.canGoBack()
+ binding.panicModeForward.isEnabled = binding.panicModeWebview.canGoForward()
+ onBackPressedCallback.isEnabled = binding.panicModeWebview.canGoBack()
+ }
+ }
+ loadUrl(arguments?.getString(PANIC_URL).orEmpty())
+ }
+ }
+
+ override fun onDestroy() {
+ webkitCookieManagerProxy.webkitCookieManager?.flush()
+ webView?.destroy()
+ super.onDestroy()
+ }
+}
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..62544f83 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
@@ -3,7 +3,9 @@ package io.github.wulkanowy.ui.modules.settings.appearance
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
+import androidx.core.os.bundleOf
import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SeekBarPreference
import com.yariksoffice.lingver.Lingver
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
@@ -29,13 +31,31 @@ class AppearanceFragment : PreferenceFragmentCompat(),
override val titleStringId get() = R.string.pref_settings_appearance_title
+ companion object {
+ fun withFocusedPreference(key: String) = AppearanceFragment().apply {
+ arguments = bundleOf(FOCUSED_KEY to key)
+ }
+
+ private const val FOCUSED_KEY = "focusedKey"
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.onAttachView(this)
+ arguments?.getString(FOCUSED_KEY)?.let { scrollToPreference(it) }
}
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/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt
index a957ef79..5cb6c401 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt
@@ -7,27 +7,28 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.databinding.ItemTimetableBinding
import io.github.wulkanowy.databinding.ItemTimetableEmptyBinding
+import io.github.wulkanowy.databinding.ItemTimetableMainAdditionalBinding
import io.github.wulkanowy.databinding.ItemTimetableSmallBinding
+import io.github.wulkanowy.utils.SyncListAdapter
import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
class TimetableAdapter @Inject constructor() :
- ListAdapter(differ) {
+ SyncListAdapter(Differ) {
override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
- return when (TimetableItemType.values()[viewType]) {
+ return when (TimetableItemType.entries[viewType]) {
TimetableItemType.SMALL -> SmallViewHolder(
ItemTimetableSmallBinding.inflate(inflater, parent, false)
)
@@ -39,6 +40,10 @@ class TimetableAdapter @Inject constructor() :
TimetableItemType.EMPTY -> EmptyViewHolder(
ItemTimetableEmptyBinding.inflate(inflater, parent, false)
)
+
+ TimetableItemType.ADDITIONAL -> AdditionalViewHolder(
+ ItemTimetableMainAdditionalBinding.inflate(inflater, parent, false)
+ )
}
}
@@ -61,16 +66,30 @@ class TimetableAdapter @Inject constructor() :
binding = holder.binding,
item = getItem(position) as TimetableItem.Small,
)
-
is NormalViewHolder -> bindNormalView(
binding = holder.binding,
item = getItem(position) as TimetableItem.Normal,
)
-
is EmptyViewHolder -> bindEmptyView(
binding = holder.binding,
item = getItem(position) as TimetableItem.Empty,
)
+
+ is AdditionalViewHolder -> bindAdditionalView(
+ binding = holder.binding,
+ item = getItem(position) as TimetableItem.Additional,
+ )
+ }
+ }
+
+ private fun bindAdditionalView(
+ binding: ItemTimetableMainAdditionalBinding,
+ item: TimetableItem.Additional
+ ) {
+ with(binding) {
+ timetableItemSubject.text = item.additional.subject
+ timetableItemTimeStart.text = item.additional.start.toFormattedString("HH:mm")
+ timetableItemTimeFinish.text = item.additional.end.toFormattedString("HH:mm")
}
}
@@ -79,6 +98,7 @@ class TimetableAdapter @Inject constructor() :
with(binding) {
timetableSmallItemNumber.text = lesson.number.toString()
+ timetableSmallItemNumber.isVisible = item.isLessonNumberVisible
timetableSmallItemSubject.text = lesson.subject
timetableSmallItemTimeStart.text = lesson.start.toFormattedString("HH:mm")
timetableSmallItemRoom.text = lesson.room
@@ -97,6 +117,7 @@ class TimetableAdapter @Inject constructor() :
with(binding) {
timetableItemNumber.text = lesson.number.toString()
+ timetableItemNumber.isVisible = item.isLessonNumberVisible
timetableItemSubject.text = lesson.subject
timetableItemGroup.text = lesson.group
timetableItemRoom.text = lesson.room
@@ -305,31 +326,32 @@ class TimetableAdapter @Inject constructor() :
private class EmptyViewHolder(val binding: ItemTimetableEmptyBinding) :
RecyclerView.ViewHolder(binding.root)
- companion object {
- private val differ = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean =
- when {
- oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> {
- oldItem.lesson.start == newItem.lesson.start
- }
+ private class AdditionalViewHolder(val binding: ItemTimetableMainAdditionalBinding) :
+ RecyclerView.ViewHolder(binding.root)
- oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal -> {
- oldItem.lesson.start == newItem.lesson.start
- }
-
- else -> oldItem == newItem
+ private object Differ : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean =
+ when {
+ oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> {
+ oldItem.lesson.start == newItem.lesson.start
}
- override fun areContentsTheSame(oldItem: TimetableItem, newItem: TimetableItem) =
- oldItem == newItem
+ oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal -> {
+ oldItem.lesson.start == newItem.lesson.start
+ }
- override fun getChangePayload(oldItem: TimetableItem, newItem: TimetableItem): Any? {
- return if (oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal) {
- if (oldItem.lesson == newItem.lesson && oldItem.showGroupsInPlan == newItem.showGroupsInPlan && oldItem.timeLeft != newItem.timeLeft) {
- "time_left"
- } else super.getChangePayload(oldItem, newItem)
- } else super.getChangePayload(oldItem, newItem)
+ else -> oldItem == newItem
}
+
+ override fun areContentsTheSame(oldItem: TimetableItem, newItem: TimetableItem) =
+ oldItem == newItem
+
+ override fun getChangePayload(oldItem: TimetableItem, newItem: TimetableItem): Any? {
+ return if (oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal) {
+ if (oldItem.lesson == newItem.lesson && oldItem.showGroupsInPlan == newItem.showGroupsInPlan && oldItem.timeLeft != newItem.timeLeft) {
+ "time_left"
+ } else super.getChangePayload(oldItem, newItem)
+ } else super.getChangePayload(oldItem, newItem)
}
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt
index 0e645911..b73e7c26 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt
@@ -21,7 +21,11 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.timetable.additional.AdditionalLessonsFragment
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
+import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
+import io.github.wulkanowy.utils.openMaterialDatePicker
import java.time.LocalDate
import javax.inject.Inject
@@ -104,8 +108,11 @@ class TimetableFragment : BaseFragment(R.layout.fragme
}
}
- override fun updateData(data: List) {
- timetableAdapter.submitList(data)
+ override fun updateData(data: List, isDayChanged: Boolean) {
+ when {
+ isDayChanged -> timetableAdapter.recreate(data)
+ else -> timetableAdapter.submitList(data)
+ }
}
override fun clearData() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt
index 105ece38..93290ba2 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt
@@ -1,18 +1,21 @@
package io.github.wulkanowy.ui.modules.timetable
import io.github.wulkanowy.data.db.entities.Timetable
+import io.github.wulkanowy.data.db.entities.TimetableAdditional
import java.time.Duration
sealed class TimetableItem(val type: TimetableItemType) {
data class Small(
val lesson: Timetable,
+ val isLessonNumberVisible: Boolean,
val onClick: (Timetable) -> Unit,
) : TimetableItem(TimetableItemType.SMALL)
data class Normal(
val lesson: Timetable,
val showGroupsInPlan: Boolean,
+ val isLessonNumberVisible: Boolean,
val timeLeft: TimeLeft?,
val onClick: (Timetable) -> Unit,
) : TimetableItem(TimetableItemType.NORMAL)
@@ -21,6 +24,10 @@ sealed class TimetableItem(val type: TimetableItemType) {
val numFrom: Int,
val numTo: Int
) : TimetableItem(TimetableItemType.EMPTY)
+
+ data class Additional(
+ val additional: TimetableAdditional,
+ ) : TimetableItem(TimetableItemType.ADDITIONAL)
}
data class TimeLeft(
@@ -32,5 +39,6 @@ data class TimeLeft(
enum class TimetableItemType {
SMALL,
NORMAL,
- EMPTY
+ EMPTY,
+ ADDITIONAL,
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
index e83f2517..c00bdc3e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt
@@ -4,6 +4,9 @@ import android.os.Handler
import android.os.Looper
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Timetable
+import io.github.wulkanowy.data.db.entities.TimetableAdditional
+import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode.BELOW
+import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode.NONE
import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS
import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS
import io.github.wulkanowy.data.enums.TimetableMode
@@ -14,6 +17,7 @@ import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceIntermediate
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.onResourceSuccess
+import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
@@ -57,6 +61,7 @@ class TimetablePresenter @Inject constructor(
private var initialDate: LocalDate? = null
private var isWeekendHasLessons: Boolean = false
+ private var isEduOne: Boolean = false
var currentDate: LocalDate? = null
private set
@@ -80,7 +85,7 @@ class TimetablePresenter @Inject constructor(
} else currentDate?.previousSchoolDay
reloadView(date ?: return)
- loadData()
+ loadData(isDayChanged = true)
}
fun onNextDay() {
@@ -89,7 +94,7 @@ class TimetablePresenter @Inject constructor(
} else currentDate?.nextSchoolDay
reloadView(date ?: return)
- loadData()
+ loadData(isDayChanged = true)
}
fun onPickDate() {
@@ -103,7 +108,7 @@ class TimetablePresenter @Inject constructor(
fun onSwipeRefresh() {
Timber.i("Force refreshing the timetable")
- loadData(true)
+ loadData(forceRefresh = true)
}
fun onRetry() {
@@ -111,7 +116,7 @@ class TimetablePresenter @Inject constructor(
showErrorView(false)
showProgress(true)
}
- loadData(true)
+ loadData(forceRefresh = true)
}
fun onDetailsClick() {
@@ -144,11 +149,12 @@ class TimetablePresenter @Inject constructor(
return true
}
- private fun loadData(forceRefresh: Boolean = false) {
+ private fun loadData(forceRefresh: Boolean = false, isDayChanged: Boolean = false) {
flatResourceFlow {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
+ isEduOne = student.isEduOne == true
checkInitialAndCurrentDate(semester)
timetableRepository.getTimetable(
student = student,
@@ -167,9 +173,9 @@ class TimetablePresenter @Inject constructor(
enableSwipe(true)
showProgress(false)
showErrorView(false)
- showContent(it.lessons.isNotEmpty())
- showEmpty(it.lessons.isEmpty())
- updateData(it.lessons)
+ updateData(it, isDayChanged)
+ showContent(it.lessons.isNotEmpty() || it.additional.isNotEmpty())
+ showEmpty(it.lessons.isEmpty() && it.additional.isEmpty())
setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content)
reloadNavigation()
}
@@ -214,66 +220,97 @@ class TimetablePresenter @Inject constructor(
}
}
- private fun updateData(lessons: List) {
+ private fun updateData(lessons: TimetableFull, isDayChanged: Boolean) {
tickTimer?.cancel()
- if (currentDate != now()) {
- view?.updateData(createItems(lessons))
- } else {
- tickTimer = timer(period = 2_000) {
+ view?.updateData(createItems(lessons), isDayChanged)
+ if (currentDate == now()) {
+ tickTimer = timer(period = 2_000, initialDelay = 2_000) {
Handler(Looper.getMainLooper()).post {
- view?.updateData(createItems(lessons))
+ view?.updateData(createItems(lessons), isDayChanged)
}
}
}
}
- private fun createItems(items: List): List {
- val filteredItems = items
- .filter {
- if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) {
- it.isStudentPlan
- } else true
- }.sortedWith(
- compareBy({ item -> item.number }, { item -> !item.isStudentPlan })
- )
+ private sealed class Item(
+ val isStudentPlan: Boolean,
+ val start: Instant,
+ val number: Int?,
+ ) {
+ class Lesson(val lesson: Timetable) :
+ Item(lesson.isStudentPlan, lesson.start, lesson.number)
+
+ class Additional(val additional: TimetableAdditional) : Item(true, additional.start, null)
+ }
+
+ private fun createItems(fullTimetable: TimetableFull): List {
+ val showAdditionalLessonsInPlan = prefRepository.showAdditionalLessonsInPlan
+ val allItems =
+ fullTimetable.lessons.map(Item::Lesson) + fullTimetable.additional.map(Item::Additional)
+ .takeIf { showAdditionalLessonsInPlan != NONE }.orEmpty()
+
+ val filteredItems = allItems.filter {
+ if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) {
+ it.isStudentPlan
+ } else true
+ }.sortedWith(
+ (compareBy- { it is Item.Additional }
+ .takeIf { showAdditionalLessonsInPlan == BELOW } ?: EmptyComparator())
+ .thenBy { it.start }
+ .thenBy { !it.isStudentPlan }
+ )
var prevNum = when (prefRepository.showTimetableGaps) {
BETWEEN_AND_BEFORE_LESSONS -> 0
else -> null
}
+ var prevIsAdditional = false
return buildList {
filteredItems.forEachIndexed { i, it ->
- if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) {
- val emptyLesson = TimetableItem.Empty(
- numFrom = prevNum!! + 1,
- numTo = it.number - 1
- )
- add(emptyLesson)
+ if (prefRepository.showTimetableGaps != NO_GAPS) {
+ if (prevNum != null && it.number != null && it.number > prevNum!! + 1) {
+ if (!prevIsAdditional) {
+ // Additional lessons do count as a lesson so don't add empty lessons
+ // when there is an additional lesson present
+ val emptyLesson = TimetableItem.Empty(
+ numFrom = prevNum!! + 1, numTo = it.number - 1
+ )
+ add(emptyLesson)
+ }
+ }
+ prevNum = it.number
+ prevIsAdditional = it is Item.Additional
}
- if (it.isStudentPlan) {
- val normalLesson = TimetableItem.Normal(
- lesson = it,
- showGroupsInPlan = prefRepository.showGroupsInPlan,
- timeLeft = filteredItems.getTimeLeftForLesson(it, i),
- onClick = ::onTimetableItemSelected
- )
- add(normalLesson)
- } else {
- val smallLesson = TimetableItem.Small(
- lesson = it,
- onClick = ::onTimetableItemSelected
- )
- add(smallLesson)
+ if (it is Item.Lesson) {
+ if (it.isStudentPlan) {
+ val normalLesson = TimetableItem.Normal(
+ lesson = it.lesson,
+ showGroupsInPlan = prefRepository.showGroupsInPlan,
+ timeLeft = filteredItems.getTimeLeftForLesson(it.lesson, i),
+ onClick = ::onTimetableItemSelected,
+ isLessonNumberVisible = !isEduOne
+ )
+ add(normalLesson)
+ } else {
+ val smallLesson = TimetableItem.Small(
+ lesson = it.lesson,
+ onClick = ::onTimetableItemSelected,
+ isLessonNumberVisible = !isEduOne
+ )
+ add(smallLesson)
+ }
+ } else if (it is Item.Additional) {
+ // If the user disabled showing additional lessons, they would've been filtered
+ // out already, so there's no need to check it again.
+ add(TimetableItem.Additional(it.additional))
}
-
- prevNum = it.number
}
}
}
- private fun List.getTimeLeftForLesson(lesson: Timetable, index: Int): TimeLeft {
+ private fun List
- .getTimeLeftForLesson(lesson: Timetable, index: Int): TimeLeft {
val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(index))
return TimeLeft(
until = lesson.until.plusMinutes(1).takeIf { isShowTimeUntil },
@@ -282,11 +319,20 @@ class TimetablePresenter @Inject constructor(
)
}
- private fun List.getPreviousLesson(position: Int): Instant? {
- return filter { it.isStudentPlan }
- .getOrNull(position - 1 - filterIndexed { i, item -> i < position && !item.isStudentPlan }.size)
+ private fun List
- .getPreviousLesson(position: Int): Instant? {
+ val lessonAdditionalOffset = filterIndexed { i, item ->
+ i < position && item is Item.Additional
+ }.size
+ val lessonStudentPlanOffset = filterIndexed { i, item ->
+ i < position && !item.isStudentPlan
+ }.size
+ val lessonIndex = position - 1 - lessonAdditionalOffset - lessonStudentPlanOffset
+
+ return filterIsInstance()
+ .filter { it.isStudentPlan }
+ .getOrNull(lessonIndex)
?.let {
- if (!it.canceled && it.isStudentPlan) it.end
+ if (!it.lesson.canceled && it.isStudentPlan) it.lesson.end
else null
}
}
@@ -339,3 +385,7 @@ class TimetablePresenter @Inject constructor(
super.onDetachView()
}
}
+
+private class EmptyComparator : Comparator {
+ override fun compare(o1: T, o2: T) = 0
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt
index 40190d51..f4d5b762 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt
@@ -12,7 +12,7 @@ interface TimetableView : BaseView {
fun initView()
- fun updateData(data: List)
+ fun updateData(data: List, isDayChanged: Boolean)
fun updateNavigationDay(date: String)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt
index faa833c2..bf6be56f 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt
@@ -13,7 +13,11 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.timetable.additional.add.AdditionalLessonAddDialog
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
+import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
+import io.github.wulkanowy.utils.openMaterialDatePicker
import java.time.LocalDate
import javax.inject.Inject
@@ -132,8 +136,12 @@ class AdditionalLessonsFragment :
binding.additionalLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
}
- override fun showAddAdditionalLessonDialog() {
- (activity as? MainActivity)?.showDialogFragment(AdditionalLessonAddDialog.newInstance())
+ override fun showAddAdditionalLessonDialog(currentDate: LocalDate) {
+ (activity as? MainActivity)?.showDialogFragment(
+ AdditionalLessonAddDialog.newInstance(
+ currentDate
+ )
+ )
}
override fun showDatePickerDialog(selectedDate: LocalDate) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt
index d0a01b38..16ec9746 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt
@@ -1,14 +1,27 @@
package io.github.wulkanowy.ui.modules.timetable.additional
import android.annotation.SuppressLint
-import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.TimetableAdditional
+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.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
+import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
-import io.github.wulkanowy.utils.*
+import io.github.wulkanowy.utils.AnalyticsHelper
+import io.github.wulkanowy.utils.capitalise
+import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
+import io.github.wulkanowy.utils.isHolidays
+import io.github.wulkanowy.utils.nextOrSameSchoolDay
+import io.github.wulkanowy.utils.nextSchoolDay
+import io.github.wulkanowy.utils.previousSchoolDay
+import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
@@ -22,11 +35,14 @@ class AdditionalLessonsPresenter @Inject constructor(
errorHandler: ErrorHandler,
private val semesterRepository: SemesterRepository,
private val timetableRepository: TimetableRepository,
+ private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase,
private val analytics: AnalyticsHelper
) : BasePresenter(errorHandler, studentRepository) {
private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay
+ private var isWeekendHasLessons: Boolean = false
+
lateinit var currentDate: LocalDate
private set
@@ -43,12 +59,18 @@ class AdditionalLessonsPresenter @Inject constructor(
}
fun onPreviousDay() {
- loadData(currentDate.previousSchoolDay)
+ val date = if (isWeekendHasLessons) {
+ currentDate.minusDays(1)
+ } else currentDate.previousSchoolDay
+ loadData(date)
reloadView()
}
fun onNextDay() {
- loadData(currentDate.nextSchoolDay)
+ val date = if (isWeekendHasLessons) {
+ currentDate.plusDays(1)
+ } else currentDate.nextSchoolDay
+ loadData(date)
reloadView()
}
@@ -57,7 +79,7 @@ class AdditionalLessonsPresenter @Inject constructor(
}
fun onAdditionalLessonAddButtonClicked() {
- view?.showAddAdditionalLessonDialog()
+ view?.showAddAdditionalLessonDialog(currentDate)
}
fun onDateSet(year: Int, month: Int, day: Int) {
@@ -131,6 +153,8 @@ class AdditionalLessonsPresenter @Inject constructor(
flatResourceFlow {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
+
+ isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(semester, currentDate)
timetableRepository.getTimetable(
student = student,
semester = semester,
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt
index 76d37b75..291c1217 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt
@@ -36,7 +36,7 @@ interface AdditionalLessonsView : BaseView {
fun showDatePickerDialog(selectedDate: LocalDate)
- fun showAddAdditionalLessonDialog()
+ fun showAddAdditionalLessonDialog(currentDate: LocalDate)
fun showSuccessMessage()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt
index 13471997..9470c910 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.timetable.additional.add
import android.app.Dialog
import android.os.Bundle
import android.view.View
+import androidx.core.os.bundleOf
import androidx.core.widget.doOnTextChanged
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.timepicker.MaterialTimePicker
@@ -26,10 +27,12 @@ class AdditionalLessonAddDialog : BaseDialogFragment
lateinit var presenter: AdditionalLessonAddPresenter
companion object {
- fun newInstance() = AdditionalLessonAddDialog()
+ const val ARGUMENT_KEY = "additional_lesson_default_date"
+ fun newInstance(defaultDate: LocalDate) = AdditionalLessonAddDialog().apply {
+ arguments = bundleOf(ARGUMENT_KEY to defaultDate.toEpochDay())
+ }
}
-
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext(), theme)
.setView(
@@ -40,10 +43,13 @@ class AdditionalLessonAddDialog : BaseDialogFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ arguments?.getLong(ARGUMENT_KEY)?.let(LocalDate::ofEpochDay)?.let {
+ presenter.onDateSelected(it)
+ }
presenter.onAttachView(this)
}
- override fun initView() {
+ override fun initView(selectedDate: LocalDate) {
with(binding) {
additionalLessonDialogStartEdit.doOnTextChanged { _, _, _, _ ->
additionalLessonDialogStart.isErrorEnabled = false
@@ -53,6 +59,7 @@ class AdditionalLessonAddDialog : BaseDialogFragment
additionalLessonDialogEnd.isErrorEnabled = false
additionalLessonDialogEnd.error = null
}
+ additionalLessonDialogDateEdit.setText(selectedDate.toFormattedString())
additionalLessonDialogDateEdit.doOnTextChanged { _, _, _, _ ->
additionalLessonDialogDate.isErrorEnabled = false
additionalLessonDialogDate.error = null
@@ -61,7 +68,6 @@ class AdditionalLessonAddDialog : BaseDialogFragment
additionalLessonDialogContent.isErrorEnabled = false
additionalLessonDialogContent.error = null
}
-
additionalLessonDialogAdd.setOnClickListener {
presenter.onAddAdditionalClicked(
start = additionalLessonDialogStartEdit.text?.toString(),
@@ -155,7 +161,9 @@ class AdditionalLessonAddDialog : BaseDialogFragment
.build()
timePicker.addOnPositiveButtonClickListener {
- onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute))
+ if (isAdded) {
+ onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute))
+ }
}
if (!parentFragmentManager.isStateSaved) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt
index c207165d..db59a2ab 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt
@@ -10,9 +10,12 @@ import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
import io.github.wulkanowy.utils.toLocalDate
import kotlinx.coroutines.launch
import timber.log.Timber
-import java.time.*
+import java.time.LocalDate
+import java.time.LocalTime
+import java.time.ZoneId
+import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
-import java.util.*
+import java.util.UUID
import javax.inject.Inject
class AdditionalLessonAddPresenter @Inject constructor(
@@ -30,7 +33,7 @@ class AdditionalLessonAddPresenter @Inject constructor(
override fun onAttachView(view: AdditionalLessonAddView) {
super.onAttachView(view)
- view.initView()
+ view.initView(selectedDate)
Timber.i("AdditionalLesson details view was initialized")
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt
index 0df53815..8d9678e7 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt
@@ -6,7 +6,7 @@ import java.time.LocalTime
interface AdditionalLessonAddView : BaseView {
- fun initView()
+ fun initView(selectedDate: LocalDate)
fun closeDialog()
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..55621bc5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt
@@ -22,6 +22,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
+import io.github.wulkanowy.data.repositories.isEndDateReached
import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
@@ -46,11 +47,8 @@ class TimetableWidgetFactory(
) : RemoteViewsService.RemoteViewsFactory {
private var items = emptyList()
-
private var timetableCanceledColor: Int? = null
-
private var textColor: Int? = null
-
private var timetableChangeColor: Int? = null
override fun getLoadingView() = null
@@ -74,6 +72,8 @@ class TimetableWidgetFactory(
items = emptyList()
+ if (isEndDateReached) return
+
runBlocking {
runCatching {
val student = getStudent(studentId) ?: return@runBlocking
@@ -81,7 +81,7 @@ class TimetableWidgetFactory(
val lessons = getLessons(student, semester, date)
val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
- createItems(lessons, lastSync)
+ createItems(lessons, lastSync, !(student.isEduOne ?: false))
}
.onFailure {
items = listOf(TimetableWidgetItem.Error(it))
@@ -104,14 +104,22 @@ 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 }
+ return lessons.sortedBy { it.start }
}
private fun createItems(
lessons: List,
lastSync: Instant?,
+ isEduOne: Boolean
): List {
var prevNum = when (prefRepository.showTimetableGaps) {
BETWEEN_AND_BEFORE_LESSONS -> 0
@@ -127,7 +135,7 @@ class TimetableWidgetFactory(
)
add(emptyItem)
}
- add(TimetableWidgetItem.Normal(it))
+ add(TimetableWidgetItem.Normal(it, isEduOne))
prevNum = it.number
}
add(TimetableWidgetItem.Synchronized(lastSync ?: Instant.MIN))
@@ -155,9 +163,11 @@ class TimetableWidgetFactory(
val lessonStartTime = lesson.start.toFormattedString(TIME_FORMAT_STYLE)
val lessonEndTime = lesson.end.toFormattedString(TIME_FORMAT_STYLE)
+ val lessonNumberVisibility = if (item.isLessonNumberVisible) VISIBLE else GONE
val remoteViews = RemoteViews(context.packageName, R.layout.item_widget_timetable).apply {
setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString())
+ setViewVisibility(R.id.timetableWidgetItemNumber, lessonNumberVisibility)
setTextViewText(R.id.timetableWidgetItemTimeStart, lessonStartTime)
setTextViewText(R.id.timetableWidgetItemTimeFinish, lessonEndTime)
setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt
index fb02f891..d4c2cfc0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt
@@ -7,6 +7,7 @@ sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) {
data class Normal(
val lesson: Timetable,
+ val isLessonNumberVisible: Boolean,
) : TimetableWidgetItem(TimetableWidgetItemType.NORMAL)
data class Empty(
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/RefreshUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt
index 72129751..4cce4be4 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt
@@ -23,13 +23,17 @@ fun getRefreshKey(name: String, semester: Semester): String {
}
fun getRefreshKey(name: String, student: Student): String {
- return "${name}_${student.userLoginId}"
+ return "${name}_${student.studentId}"
}
fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): String {
return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}"
}
+fun getRefreshKey(name: String): String {
+ return name
+}
+
class AutoRefreshHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val sharedPref: SharedPrefProvider
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/SemesterExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt
index e3b8a3b4..6cfc4fa1 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt
@@ -18,7 +18,7 @@ fun Semester.isCurrent(now: LocalDate = now()): Boolean {
}
fun List.getCurrentOrLast(): Semester {
- if (isEmpty()) throw RuntimeException("Empty semester list")
+ if (isEmpty()) throw IllegalStateException("Empty semester list")
// when there is only one current semester
singleOrNull { it.isCurrent() }?.let { return it }
diff --git a/app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt b/app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt
new file mode 100644
index 00000000..e9135f49
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt
@@ -0,0 +1,66 @@
+package io.github.wulkanowy.utils
+
+import android.annotation.SuppressLint
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * Custom alternative to androidx.recyclerview.widget.ListAdapter. ListAdapter is asynchronous which
+ * caused data race problems in views when a Resource.Error arrived shortly after
+ * Resource.Intermediate/Success - occasionally in that case the user could see both the Resource's
+ * data and an error message one on top of the other. This is synchronized by design to avoid that
+ * problem, however it retains the quality of life improvements of the original.
+ */
+abstract class SyncListAdapter private constructor(
+ private val updateStrategy: SyncListAdapter.(List) -> Unit
+) : RecyclerView.Adapter() {
+
+ constructor(differ: DiffUtil.ItemCallback) : this({ newItems ->
+ val diffResult = DiffUtil.calculateDiff(toCallback(differ, items, newItems))
+ items = newItems
+ diffResult.dispatchUpdatesTo(this)
+ })
+
+ var items = emptyList()
+ private set
+
+ final override fun getItemCount() = items.size
+
+ fun getItem(position: Int): T {
+ return items[position]
+ }
+
+ /**
+ * Updates all items, same as submitList, however also disables animations temporarily.
+ * This prevents a flashing effect on some views. Should be used in favor of submitList when
+ * all data is changed (e.g. the selected day changes in timetable causing all lessons to change).
+ */
+ @SuppressLint("NotifyDataSetChanged")
+ fun recreate(data: List) {
+ items = data
+ notifyDataSetChanged()
+ }
+
+ fun submitList(data: List) {
+ updateStrategy(data.toList())
+ }
+
+ private fun toCallback(
+ itemCallback: DiffUtil.ItemCallback