diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index cb2f4119..0ac66f64 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -18,18 +18,9 @@
-
-
-
-
-
-
-
+
-
-
-
@@ -143,13 +134,11 @@
+
-
-
-
diff --git a/app/build.gradle b/app/build.gradle
index 6ccd6152..f2efb421 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -11,19 +11,22 @@ apply from: 'hooks.gradle'
android {
compileSdkVersion 30
- buildToolsVersion '30.0.2'
+ buildToolsVersion '30.0.3'
defaultConfig {
applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17
targetSdkVersion 30
- versionCode 81
- versionName "0.24.3"
+ versionCode 82
+ versionName "0.25.0"
multiDexEnabled true
- resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
+
+ resValue "string", "app_name", "Wulkanowy"
+ buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
+
manifestPlaceholders = [
firebase_enabled: project.hasProperty("enableFirebase")
]
@@ -126,19 +129,20 @@ play {
serviceAccountCredentials = file('key.p12')
defaultToAppBundles = false
track = 'alpha'
- updatePriority = 5
+ updatePriority = 3
}
ext {
- work_manager = "2.4.0"
- room = "2.2.6"
+ work_manager = "2.5.0"
+ work_hilt = "1.0.0-alpha03"
+ room = "2.3.0-beta01"
chucker = "3.4.0"
mockk = "1.10.5"
moshi = "1.11.0"
}
dependencies {
- implementation "io.github.wulkanowy:sdk:0.24.1"
+ implementation "io.github.wulkanowy:sdk:0.25.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
@@ -159,10 +163,9 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
- implementation "com.google.android.material:material:1.2.1"
+ implementation "com.google.android.material:material:1.3.0-rc01"
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
- implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager"
@@ -175,12 +178,12 @@ dependencies {
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
- implementation 'androidx.hilt:hilt-work:1.0.0-alpha02'
- kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
+ implementation "androidx.hilt:hilt-work:$work_hilt"
+ kapt "androidx.hilt:hilt-compiler:$work_hilt"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation "com.ncapdevi:frag-nav:3.3.0"
- implementation "com.github.YarikSOffice:lingver:1.2.2"
+ implementation "com.github.YarikSOffice:lingver:1.3.0"
implementation "com.squareup.moshi:moshi:$moshi"
implementation "com.squareup.moshi:moshi-adapters:$moshi"
@@ -194,7 +197,7 @@ dependencies {
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
- playImplementation platform('com.google.firebase:firebase-bom:26.3.0')
+ playImplementation platform('com.google.firebase:firebase-bom:26.4.0')
playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx"
@@ -204,7 +207,7 @@ dependencies {
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
hmsImplementation 'com.huawei.hms:hianalytics:5.1.0.301'
- hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.4.2.301'
+ hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.5.0.200'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
@@ -214,6 +217,7 @@ dependencies {
testImplementation "junit:junit:4.13.1"
testImplementation "io.mockk:mockk:$mockk"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
+ testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
androidTestImplementation "androidx.test:core:1.3.0"
androidTestImplementation "androidx.test:runner:1.3.0"
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json
new file mode 100644
index 00000000..4935a901
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json
@@ -0,0 +1,2136 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 31,
+ "identityHash": "d642512ffa5fe81ae9308c9c55612539",
+ "entities": [
+ {
+ "tableName": "Students",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scrapperBaseUrl",
+ "columnName": "scrapper_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mobileBaseUrl",
+ "columnName": "mobile_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginType",
+ "columnName": "login_type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginMode",
+ "columnName": "login_mode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "certificateKey",
+ "columnName": "certificate_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "privateKey",
+ "columnName": "private_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isParent",
+ "columnName": "is_parent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "user_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "student_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolSymbol",
+ "columnName": "school_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "school_short",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolName",
+ "columnName": "school_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "className",
+ "columnName": "class_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isCurrent",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registrationDate",
+ "columnName": "registration_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_Students_email_symbol_student_id_school_id_class_id",
+ "unique": true,
+ "columnNames": [
+ "email",
+ "symbol",
+ "student_id",
+ "school_id",
+ "class_id"
+ ],
+ "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}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "current",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryName",
+ "columnName": "diary_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolYear",
+ "columnName": "school_year",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterName",
+ "columnName": "semester_name",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unitId",
+ "columnName": "unit_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_Semesters_student_id_diary_id_semester_id",
+ "unique": true,
+ "columnNames": [
+ "student_id",
+ "diary_id",
+ "semester_id"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Exams",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Timetable",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Attendance",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "AttendanceSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Grades",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `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)",
+ "fields": [
+ {
+ "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
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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, `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)",
+ "fields": [
+ {
+ "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
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradePartialStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesPointsStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradeSemesterStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Messages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "readBy",
+ "columnName": "read_by",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realId",
+ "columnName": "real_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderId",
+ "columnName": "sender_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "recipient",
+ "columnName": "recipient_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "folderId",
+ "columnName": "folder_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "removed",
+ "columnName": "removed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasAttachments",
+ "columnName": "has_attachments",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageAttachments",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))",
+ "fields": [
+ {
+ "fieldPath": "realId",
+ "columnName": "real_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "oneDriveId",
+ "columnName": "one_drive_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "filename",
+ "columnName": "filename",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "real_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `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)",
+ "fields": [
+ {
+ "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
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Homework",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDone",
+ "columnName": "is_done",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Subjects",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "LuckyNumbers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "CompletedLesson",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ReportingUnits",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unitId",
+ "columnName": "real_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "short",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderId",
+ "columnName": "sender_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderName",
+ "columnName": "sender_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "roles",
+ "columnName": "roles",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Recipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realId",
+ "columnName": "real_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realName",
+ "columnName": "real_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginId",
+ "columnName": "login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unitId",
+ "columnName": "unit_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "role",
+ "columnName": "role",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hash",
+ "columnName": "hash",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MobileDevices",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deviceId",
+ "columnName": "device_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Teachers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "School",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Conferences",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimetableAdditional",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "StudentInfo",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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, `first_guardian_full_name` TEXT NOT NULL, `first_guardian_kinship` TEXT NOT NULL, `first_guardian_address` TEXT NOT NULL, `first_guardian_phones` TEXT NOT NULL, `first_guardian_email` TEXT NOT NULL, `second_guardian_full_name` TEXT NOT NULL, `second_guardian_kinship` TEXT NOT NULL, `second_guardian_address` TEXT NOT NULL, `second_guardian_phones` TEXT NOT NULL, `second_guardian_email` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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": "firstGuardian.fullName",
+ "columnName": "first_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.kinship",
+ "columnName": "first_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.address",
+ "columnName": "first_guardian_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.phones",
+ "columnName": "first_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.email",
+ "columnName": "first_guardian_email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.fullName",
+ "columnName": "second_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.kinship",
+ "columnName": "second_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.address",
+ "columnName": "second_guardian_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.phones",
+ "columnName": "second_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.email",
+ "columnName": "second_guardian_email",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "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, 'd642512ffa5fe81ae9308c9c55612539')"
+ ]
+ }
+}
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json
new file mode 100644
index 00000000..3621be48
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json
@@ -0,0 +1,2142 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 32,
+ "identityHash": "9531cdc8b3f0e62db5ab6ebe66837a28",
+ "entities": [
+ {
+ "tableName": "Students",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "nick",
+ "columnName": "nick",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scrapperBaseUrl",
+ "columnName": "scrapper_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mobileBaseUrl",
+ "columnName": "mobile_base_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginType",
+ "columnName": "login_type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginMode",
+ "columnName": "login_mode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "certificateKey",
+ "columnName": "certificate_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "privateKey",
+ "columnName": "private_key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isParent",
+ "columnName": "is_parent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "symbol",
+ "columnName": "symbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "user_login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userName",
+ "columnName": "user_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentName",
+ "columnName": "student_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolSymbol",
+ "columnName": "school_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolShortName",
+ "columnName": "school_short",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolName",
+ "columnName": "school_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "className",
+ "columnName": "class_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isCurrent",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registrationDate",
+ "columnName": "registration_date",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_Students_email_symbol_student_id_school_id_class_id",
+ "unique": true,
+ "columnNames": [
+ "email",
+ "symbol",
+ "student_id",
+ "school_id",
+ "class_id"
+ ],
+ "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}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "current",
+ "columnName": "is_current",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryId",
+ "columnName": "diary_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "diaryName",
+ "columnName": "diary_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "schoolYear",
+ "columnName": "school_year",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterId",
+ "columnName": "semester_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semesterName",
+ "columnName": "semester_name",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "classId",
+ "columnName": "class_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unitId",
+ "columnName": "unit_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_Semesters_student_id_diary_id_semester_id",
+ "unique": true,
+ "columnNames": [
+ "student_id",
+ "diary_id",
+ "semester_id"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Exams",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Timetable",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Attendance",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "AttendanceSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Grades",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `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)",
+ "fields": [
+ {
+ "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
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesSummary",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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, `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)",
+ "fields": [
+ {
+ "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
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradePartialStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradesPointsStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GradeSemesterStatistics",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Messages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "readBy",
+ "columnName": "read_by",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realId",
+ "columnName": "real_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderId",
+ "columnName": "sender_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "recipient",
+ "columnName": "recipient_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "folderId",
+ "columnName": "folder_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "removed",
+ "columnName": "removed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasAttachments",
+ "columnName": "has_attachments",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageAttachments",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))",
+ "fields": [
+ {
+ "fieldPath": "realId",
+ "columnName": "real_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "message_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "oneDriveId",
+ "columnName": "one_drive_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "filename",
+ "columnName": "filename",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "real_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `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)",
+ "fields": [
+ {
+ "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
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Homework",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDone",
+ "columnName": "is_done",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Subjects",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "LuckyNumbers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isNotified",
+ "columnName": "is_notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "CompletedLesson",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ReportingUnits",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unitId",
+ "columnName": "real_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "short",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderId",
+ "columnName": "sender_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderName",
+ "columnName": "sender_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "roles",
+ "columnName": "roles",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Recipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realId",
+ "columnName": "real_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realName",
+ "columnName": "real_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginId",
+ "columnName": "login_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unitId",
+ "columnName": "unit_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "role",
+ "columnName": "role",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hash",
+ "columnName": "hash",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MobileDevices",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLoginId",
+ "columnName": "student_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deviceId",
+ "columnName": "device_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Teachers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "School",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Conferences",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimetableAdditional",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "StudentInfo",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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, `first_guardian_full_name` TEXT NOT NULL, `first_guardian_kinship` TEXT NOT NULL, `first_guardian_address` TEXT NOT NULL, `first_guardian_phones` TEXT NOT NULL, `first_guardian_email` TEXT NOT NULL, `second_guardian_full_name` TEXT NOT NULL, `second_guardian_kinship` TEXT NOT NULL, `second_guardian_address` TEXT NOT NULL, `second_guardian_phones` TEXT NOT NULL, `second_guardian_email` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "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": "firstGuardian.fullName",
+ "columnName": "first_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.kinship",
+ "columnName": "first_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.address",
+ "columnName": "first_guardian_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.phones",
+ "columnName": "first_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "firstGuardian.email",
+ "columnName": "first_guardian_email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.fullName",
+ "columnName": "second_guardian_full_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.kinship",
+ "columnName": "second_guardian_kinship",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.address",
+ "columnName": "second_guardian_address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.phones",
+ "columnName": "second_guardian_phones",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "secondGuardian.email",
+ "columnName": "second_guardian_email",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "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, '9531cdc8b3f0e62db5ab6ebe66837a28')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/TestDispatchersProvider.kt b/app/src/androidTest/java/io/github/wulkanowy/data/TestDispatchersProvider.kt
deleted file mode 100644
index 8c4354d9..00000000
--- a/app/src/androidTest/java/io/github/wulkanowy/data/TestDispatchersProvider.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.github.wulkanowy.data
-
-import io.github.wulkanowy.utils.DispatchersProvider
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-
-class TestDispatchersProvider : DispatchersProvider() {
-
- override val backgroundThread: CoroutineDispatcher
- get() = Dispatchers.Unconfined
-}
diff --git a/app/src/debug/res/drawable-anydpi-v24/ic_stat_timetable.xml b/app/src/debug/res/drawable-anydpi-v24/ic_stat_timetable.xml
new file mode 100644
index 00000000..ac99d4a8
--- /dev/null
+++ b/app/src/debug/res/drawable-anydpi-v24/ic_stat_timetable.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/src/debug/res/drawable-hdpi/ic_stat_timetable.png b/app/src/debug/res/drawable-hdpi/ic_stat_timetable.png
new file mode 100644
index 00000000..10b27cee
Binary files /dev/null and b/app/src/debug/res/drawable-hdpi/ic_stat_timetable.png differ
diff --git a/app/src/debug/res/drawable-mdpi/ic_stat_timetable.png b/app/src/debug/res/drawable-mdpi/ic_stat_timetable.png
new file mode 100644
index 00000000..db5747b0
Binary files /dev/null and b/app/src/debug/res/drawable-mdpi/ic_stat_timetable.png differ
diff --git a/app/src/debug/res/drawable-xhdpi/ic_stat_timetable.png b/app/src/debug/res/drawable-xhdpi/ic_stat_timetable.png
new file mode 100644
index 00000000..8d985829
Binary files /dev/null and b/app/src/debug/res/drawable-xhdpi/ic_stat_timetable.png differ
diff --git a/app/src/debug/res/drawable-xxhdpi/ic_stat_timetable.png b/app/src/debug/res/drawable-xxhdpi/ic_stat_timetable.png
new file mode 100644
index 00000000..232108e8
Binary files /dev/null and b/app/src/debug/res/drawable-xxhdpi/ic_stat_timetable.png differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eaef4369..ac8d3be4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -18,6 +18,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
{
return arrayOf(
@@ -157,6 +162,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration28(),
Migration29(),
Migration30(),
+ Migration31(),
+ Migration32()
)
}
@@ -219,4 +226,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val conferenceDao: ConferenceDao
abstract val timetableAdditionalDao: TimetableAdditionalDao
+
+ abstract val studentInfoDao: StudentInfoDao
}
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 d7237ea8..e9c5f157 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
@@ -6,7 +6,9 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy.ABORT
import androidx.room.Query
import androidx.room.Transaction
+import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.StudentNick
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import javax.inject.Singleton
@@ -20,6 +22,9 @@ interface StudentDao {
@Delete
suspend fun delete(student: Student)
+ @Update(entity = Student::class)
+ suspend fun update(studentNick: StudentNick)
+
@Query("SELECT * FROM Students WHERE is_current = 1")
suspend fun loadCurrent(): Student?
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentInfoDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentInfoDao.kt
new file mode 100644
index 00000000..5ec86af1
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentInfoDao.kt
@@ -0,0 +1,15 @@
+package io.github.wulkanowy.data.db.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import io.github.wulkanowy.data.db.entities.StudentInfo
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Singleton
+
+@Singleton
+@Dao
+interface StudentInfoDao : BaseDao {
+
+ @Query("SELECT * FROM StudentInfo WHERE student_id = :studentId")
+ fun loadStudentInfo(studentId: Int): Flow
+}
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 b6c75ff8..6b60c814 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
@@ -7,7 +7,13 @@ import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDateTime
-@Entity(tableName = "Students", indices = [Index(value = ["email", "symbol", "student_id", "school_id", "class_id"], unique = true)])
+@Entity(
+ tableName = "Students",
+ indices = [Index(
+ value = ["email", "symbol", "student_id", "school_id", "class_id"],
+ unique = true
+ )]
+)
data class Student(
@ColumnInfo(name = "scrapper_base_url")
@@ -52,7 +58,7 @@ data class Student(
@ColumnInfo(name = "school_id")
val schoolSymbol: String,
- @ColumnInfo(name ="school_short")
+ @ColumnInfo(name = "school_short")
val schoolShortName: String,
@ColumnInfo(name = "school_name")
@@ -73,4 +79,6 @@ data class Student(
@PrimaryKey(autoGenerate = true)
var id: Long = 0
+
+ var nick = ""
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentInfo.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentInfo.kt
new file mode 100644
index 00000000..70d00194
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentInfo.kt
@@ -0,0 +1,85 @@
+package io.github.wulkanowy.data.db.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import io.github.wulkanowy.data.enums.Gender
+import java.io.Serializable
+import java.time.LocalDate
+
+@Entity(tableName = "StudentInfo")
+data class StudentInfo(
+
+ @ColumnInfo(name = "student_id")
+ val studentId: Int,
+
+ @ColumnInfo(name = "full_name")
+ val fullName: String,
+
+ @ColumnInfo(name = "first_name")
+ val firstName: String,
+
+ @ColumnInfo(name = "second_name")
+ val secondName: String,
+
+ val surname: String,
+
+ @ColumnInfo(name = "birth_date")
+ val birthDate: LocalDate,
+
+ @ColumnInfo(name = "birth_place")
+ val birthPlace: String,
+
+ val gender: Gender,
+
+ @ColumnInfo(name = "has_polish_citizenship")
+ val hasPolishCitizenship: Boolean,
+
+ @ColumnInfo(name = "family_name")
+ val familyName: String,
+
+ @ColumnInfo(name = "parents_names")
+ val parentsNames: String,
+
+ val address: String,
+
+ @ColumnInfo(name = "registered_address")
+ val registeredAddress: String,
+
+ @ColumnInfo(name = "correspondence_address")
+ val correspondenceAddress: String,
+
+ @ColumnInfo(name = "phone_number")
+ val phoneNumber: String,
+
+ @ColumnInfo(name = "cell_phone_number")
+ val cellPhoneNumber: String,
+
+ val email: String,
+
+ @Embedded(prefix = "first_guardian_")
+ val firstGuardian: StudentGuardian,
+
+ @Embedded(prefix = "second_guardian_")
+ val secondGuardian: StudentGuardian
+
+) : Serializable {
+
+ @PrimaryKey(autoGenerate = true)
+ var id: Long = 0
+}
+
+data class StudentGuardian(
+
+ @ColumnInfo(name = "full_name")
+ val fullName: String,
+
+ val kinship: String,
+
+ val address: String,
+
+ val phones: String,
+
+ val email: String
+) : Serializable
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNick.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNick.kt
new file mode 100644
index 00000000..71f48f7a
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNick.kt
@@ -0,0 +1,16 @@
+package io.github.wulkanowy.data.db.entities
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.io.Serializable
+
+@Entity
+data class StudentNick(
+
+ val nick: String
+
+) : Serializable {
+
+ @PrimaryKey
+ var id: Long = 0
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt
new file mode 100644
index 00000000..064a3e5b
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt
@@ -0,0 +1,42 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+class Migration31 : Migration(30, 31) {
+
+ override fun migrate(database: SupportSQLiteDatabase) {
+ database.execSQL(
+ """CREATE TABLE IF NOT EXISTS StudentInfo (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ 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,
+ first_guardian_full_name TEXT NOT NULL,
+ first_guardian_kinship TEXT NOT NULL,
+ first_guardian_address TEXT NOT NULL,
+ first_guardian_phones TEXT NOT NULL,
+ first_guardian_email TEXT NOT NULL,
+ second_guardian_full_name TEXT NOT NULL,
+ second_guardian_kinship TEXT NOT NULL,
+ second_guardian_address TEXT NOT NULL,
+ second_guardian_phones TEXT NOT NULL,
+ second_guardian_email TEXT NOT NULL)
+ """
+ )
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt
new file mode 100644
index 00000000..508485e0
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt
@@ -0,0 +1,12 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+class Migration32 : Migration(31, 32) {
+
+ override fun migrate(database: SupportSQLiteDatabase) {
+ database.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"")
+ }
+}
+
diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/Gender.kt b/app/src/main/java/io/github/wulkanowy/data/enums/Gender.kt
new file mode 100644
index 00000000..df93dcbe
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/enums/Gender.kt
@@ -0,0 +1,3 @@
+package io.github.wulkanowy.data.enums
+
+enum class Gender { MALE, FEMALE }
diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/StudentInfoMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/StudentInfoMapper.kt
new file mode 100644
index 00000000..ebdf9de2
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/mappers/StudentInfoMapper.kt
@@ -0,0 +1,38 @@
+package io.github.wulkanowy.data.mappers
+
+import io.github.wulkanowy.data.db.entities.Semester
+import io.github.wulkanowy.data.db.entities.StudentGuardian
+import io.github.wulkanowy.data.db.entities.StudentInfo
+import io.github.wulkanowy.data.enums.Gender
+import io.github.wulkanowy.sdk.pojo.StudentGuardian as SdkStudentGuardian
+import io.github.wulkanowy.sdk.pojo.StudentInfo as SdkStudentInfo
+
+fun SdkStudentInfo.mapToEntity(semester: Semester) = StudentInfo(
+ studentId = semester.studentId,
+ fullName = fullName,
+ firstName = firstName,
+ secondName = secondName,
+ surname = surname,
+ birthDate = birthDate,
+ birthPlace = birthPlace,
+ gender = Gender.valueOf(gender.name),
+ hasPolishCitizenship = hasPolishCitizenship,
+ familyName = familyName,
+ parentsNames = parentsNames,
+ address = address,
+ registeredAddress = registeredAddress,
+ correspondenceAddress = correspondenceAddress,
+ phoneNumber = phoneNumber,
+ cellPhoneNumber = phoneNumber,
+ email = email,
+ firstGuardian = guardians[0].mapToEntity(),
+ secondGuardian = guardians[1].mapToEntity()
+)
+
+fun SdkStudentGuardian.mapToEntity() = StudentGuardian(
+ fullName = fullName,
+ kinship = kinship,
+ address = address,
+ phones = phones,
+ email = email
+)
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 36d5c974..6b22b32c 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
@@ -16,16 +16,23 @@ class SchoolRepository @Inject constructor(
private val sdk: Sdk
) {
- fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
- shouldFetch = { it == null || forceRefresh },
- query = { schoolDb.load(semester.studentId, semester.classId) },
- fetch = { sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool().mapToEntity(semester) },
- saveFetchResult = { old, new ->
- if (new != old && old != null) {
- schoolDb.deleteAll(listOf(old))
- schoolDb.insertAll(listOf(new))
+ fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
+ networkBoundResource(
+ shouldFetch = { it == null || forceRefresh },
+ query = { schoolDb.load(semester.studentId, semester.classId) },
+ fetch = {
+ sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
+ .mapToEntity(semester)
+ },
+ saveFetchResult = { old, new ->
+ if (old != null && new != old) {
+ with(schoolDb) {
+ deleteAll(listOf(old))
+ insertAll(listOf(new))
+ }
+ } else if (old == null) {
+ schoolDb.insertAll(listOf(new))
+ }
}
- schoolDb.insertAll(listOf(new))
- }
- )
+ )
}
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
new file mode 100644
index 00000000..e3deb447
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt
@@ -0,0 +1,38 @@
+package io.github.wulkanowy.data.repositories
+
+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.sdk.Sdk
+import io.github.wulkanowy.utils.init
+import io.github.wulkanowy.utils.networkBoundResource
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class StudentInfoRepository @Inject constructor(
+ private val studentInfoDao: StudentInfoDao,
+ private val sdk: Sdk
+) {
+
+ fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
+ networkBoundResource(
+ shouldFetch = { it == null || forceRefresh },
+ query = { studentInfoDao.loadStudentInfo(student.studentId) },
+ fetch = {
+ sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
+ .getStudentInfo().mapToEntity(semester)
+ },
+ saveFetchResult = { old, new ->
+ if (old != null && new != old) {
+ with(studentInfoDao) {
+ deleteAll(listOf(old))
+ insertAll(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 5b80035b..55821479 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
@@ -5,6 +5,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.StudentNick
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.mappers.mapToEntities
@@ -25,49 +26,70 @@ class StudentRepository @Inject constructor(
private val sdk: Sdk
) {
- suspend fun isStudentSaved(): Boolean = getSavedStudents(false).isNotEmpty()
+ suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
- suspend fun isCurrentStudentSet(): Boolean = studentDb.loadCurrent()?.isCurrent ?: false
+ suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false
- suspend fun getStudentsApi(pin: String, symbol: String, token: String): List {
- return sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
- }
+ suspend fun getStudentsApi(
+ pin: String,
+ symbol: String,
+ token: String
+ ): List =
+ sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
- suspend fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): List {
- return sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol).mapToEntities(password)
- }
+ suspend fun getStudentsScrapper(
+ email: String,
+ password: String,
+ scrapperBaseUrl: String,
+ symbol: String
+ ): List =
+ sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
+ .mapToEntities(password)
- suspend fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): List {
- return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password)
- }
+ suspend fun getStudentsHybrid(
+ email: String,
+ password: String,
+ scrapperBaseUrl: String,
+ symbol: String
+ ): List =
+ sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password)
- suspend fun getSavedStudents(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) {
- studentDb.loadStudentsWithSemesters().map {
- it.apply {
- if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) student.password = decrypt(student.password)
+ suspend fun getSavedStudents(decryptPass: Boolean = true) =
+ withContext(dispatchers.backgroundThread) {
+ studentDb.loadStudentsWithSemesters().map {
+ it.apply {
+ if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
+ student.password = decrypt(student.password)
+ }
+ }
}
}
- }
suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) {
studentDb.loadById(id)?.apply {
- if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password)
+ if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
+ password = decrypt(password)
+ }
}
} ?: throw NoCurrentStudentException()
- suspend fun getCurrentStudent(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) {
- studentDb.loadCurrent()?.apply {
- if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password)
- }
- } ?: throw NoCurrentStudentException()
+ suspend fun getCurrentStudent(decryptPass: Boolean = true) =
+ withContext(dispatchers.backgroundThread) {
+ studentDb.loadCurrent()?.apply {
+ if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
+ password = decrypt(password)
+ }
+ }
+ } ?: throw NoCurrentStudentException()
suspend fun saveStudents(studentsWithSemesters: List): List {
semesterDb.insertSemesters(studentsWithSemesters.flatMap { it.semesters })
return withContext(dispatchers.backgroundThread) {
studentDb.insertAll(studentsWithSemesters.map { it.student }.map {
- if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context))
- else it
+ if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
+ it.copy(password = encrypt(it.password, context))
+ } else it
})
}
}
@@ -79,7 +101,7 @@ class StudentRepository @Inject constructor(
}
}
- suspend fun logoutStudent(student: Student) {
- studentDb.delete(student)
- }
+ suspend fun logoutStudent(student: Student) = studentDb.delete(student)
+
+ suspend fun updateStudentNick(studentNick: StudentNick) = studentDb.update(studentNick)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt
new file mode 100644
index 00000000..e69de29b
diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
index c0eba2f9..c5a5590b 100644
--- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
@@ -27,6 +27,7 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.DispatchersProvider
+import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.withContext
import timber.log.Timber
@@ -41,17 +42,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private val dispatchersProvider: DispatchersProvider,
) {
- private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt()
+ private fun getRequestCode(time: LocalDateTime, studentId: Int) =
+ (time.toTimestamp() * studentId).toInt()
- private fun getUpcomingLessonTime(index: Int, day: List, lesson: Timetable): LocalDateTime {
- return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
- }
+ private fun getUpcomingLessonTime(
+ index: Int,
+ day: List,
+ lesson: Timetable
+ ) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
suspend fun cancelScheduled(lessons: List, studentId: Int = 1) {
withContext(dispatchersProvider.backgroundThread) {
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
- cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
+ cancelScheduledTo(
+ upcomingTime..lesson.start,
+ getRequestCode(upcomingTime, studentId)
+ )
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
@@ -61,13 +68,18 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private fun cancelScheduledTo(range: ClosedRange, requestCode: Int) {
if (now() in range) cancelNotification()
- alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT))
+ alarmManager.cancel(
+ PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)
+ )
}
- fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
+ fun cancelNotification() =
+ NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
suspend fun scheduleNotifications(lessons: List, student: Student) {
- if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId)
+ if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
+ return cancelScheduled(lessons, student.studentId)
+ }
withContext(dispatchersProvider.backgroundThread) {
lessons.groupBy { it.date }
@@ -82,13 +94,28 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
val intent = createIntent(student, lesson, active.getOrNull(index + 1))
if (lesson.start > now()) {
- scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, active, lesson))
+ scheduleBroadcast(
+ intent,
+ student.studentId,
+ NOTIFICATION_TYPE_UPCOMING,
+ getUpcomingLessonTime(index, active, lesson)
+ )
}
if (lesson.end > now()) {
- scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
+ scheduleBroadcast(
+ intent,
+ student.studentId,
+ NOTIFICATION_TYPE_CURRENT,
+ lesson.start
+ )
if (active.lastIndex == index) {
- scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
+ scheduleBroadcast(
+ intent,
+ student.studentId,
+ NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
+ lesson.end
+ )
}
}
}
@@ -99,7 +126,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent {
return Intent(context, TimetableNotificationReceiver::class.java).apply {
putExtra(STUDENT_ID, student.studentId)
- putExtra(STUDENT_NAME, student.studentName)
+ putExtra(STUDENT_NAME, student.nickOrName)
putExtra(LESSON_ROOM, lesson.room)
putExtra(LESSON_START, lesson.start.toTimestamp())
putExtra(LESSON_END, lesson.end.toTimestamp())
@@ -109,13 +136,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
}
}
- private fun scheduleBroadcast(intent: Intent, studentId: Int, notificationType: Int, time: LocalDateTime) {
- AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, RTC_WAKEUP, time.toTimestamp(),
+ private fun scheduleBroadcast(
+ intent: Intent,
+ studentId: Int,
+ notificationType: Int,
+ time: LocalDateTime
+ ) {
+ AlarmManagerCompat.setExactAndAllowWhileIdle(
+ alarmManager, RTC_WAKEUP, time.toTimestamp(),
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
it.putExtra(LESSON_TYPE, notificationType)
}, FLAG_UPDATE_CURRENT)
)
- Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId")
+ Timber.d(
+ "TimetableNotification scheduled: type: $notificationType, subject: ${
+ intent.getStringExtra(LESSON_TITLE)
+ }, start: $time, student: $studentId"
+ )
}
}
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 a428b702..8b6bc65d 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
@@ -5,11 +5,12 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.BigTextStyle
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.core.app.NotificationManagerCompat
-import androidx.hilt.Assisted
-import androidx.hilt.work.WorkerInject
+import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
import io.github.wulkanowy.R
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
@@ -23,7 +24,8 @@ import kotlinx.coroutines.coroutineScope
import timber.log.Timber
import kotlin.random.Random
-class SyncWorker @WorkerInject constructor(
+@HiltWorker
+class SyncWorker @AssistedInject constructor(
@Assisted appContext: Context,
@Assisted workerParameters: WorkerParameters,
private val studentRepository: StudentRepository,
@@ -58,9 +60,10 @@ class SyncWorker @WorkerInject constructor(
}
val result = when {
exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
- Result.failure(Data.Builder()
- .putString("error", exceptions.map { it.stackTraceToString() }.toString())
- .build()
+ Result.failure(
+ Data.Builder()
+ .putString("error", exceptions.map { it.stackTraceToString() }.toString())
+ .build()
)
}
exceptions.isNotEmpty() -> Result.retry()
@@ -74,13 +77,16 @@ class SyncWorker @WorkerInject constructor(
}
private fun notify(result: Result) {
- notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
- .setContentTitle("Debug notification")
- .setSmallIcon(R.drawable.ic_stat_push)
- .setAutoCancel(true)
- .setColor(applicationContext.getCompatColor(R.color.colorPrimary))
- .setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))
- .setPriority(PRIORITY_DEFAULT)
- .build())
+ notificationManager.notify(
+ Random.nextInt(Int.MAX_VALUE),
+ NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
+ .setContentTitle("Debug notification")
+ .setSmallIcon(R.drawable.ic_stat_push)
+ .setAutoCancel(true)
+ .setColor(applicationContext.getCompatColor(R.color.colorPrimary))
+ .setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))
+ .setPriority(PRIORITY_DEFAULT)
+ .build()
+ )
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt
index 341ae459..f76614e1 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt
@@ -57,7 +57,7 @@ class ErrorDialog : BaseDialogFragment() {
}
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return DialogErrorBinding.inflate(inflater).apply { binding = this }.root
}
@@ -114,11 +114,17 @@ class ErrorDialog : BaseDialogFragment() {
chooserTitle = getString(R.string.about_feedback),
email = "wulkanowyinc@gmail.com",
subject = "Zgłoszenie błędu",
- body = requireContext().getString(R.string.about_feedback_template,
- "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName
+ body = requireContext().getString(
+ R.string.about_feedback_template,
+ "${appInfo.systemManufacturer} ${appInfo.systemModel}",
+ appInfo.systemVersion.toString(),
+ "${appInfo.versionName}-${appInfo.buildFlavor}"
) + "\n" + content,
onActivityNotFound = {
- requireContext().openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage)
+ requireContext().openInternetBrowser(
+ "https://github.com/wulkanowy/wulkanowy/issues",
+ ::showMessage
+ )
}
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt
index cefe6ed7..8e6130fb 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.ItemAccountBinding
import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.nickOrName
import javax.inject.Inject
class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter() {
@@ -28,7 +29,7 @@ class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter(R.layout.fragment_about
override val versionRes: Triple?
get() = context?.run {
- Triple(getString(R.string.about_version), "${appInfo.versionName} (${appInfo.versionCode})", getCompatDrawable(R.drawable.ic_all_about))
+ val buildTimestamp = appInfo.buildTimestamp.toLocalDateTime().toFormattedString("yyyy-MM-dd")
+ val versionSignature = "${appInfo.versionName}-${appInfo.buildFlavor} (${appInfo.versionCode}), $buildTimestamp"
+ Triple(getString(R.string.about_version), versionSignature, getCompatDrawable(R.drawable.ic_all_about))
}
override val creatorsRes: Triple?
@@ -65,7 +69,11 @@ class AboutFragment : BaseFragment(R.layout.fragment_about
override val homepageRes: Triple?
get() = context?.run {
- Triple(getString(R.string.about_homepage), getString(R.string.about_homepage_summary), getCompatDrawable(R.drawable.ic_about_homepage))
+ Triple(
+ getString(R.string.about_homepage),
+ getString(R.string.about_homepage_summary),
+ getCompatDrawable(R.drawable.ic_all_home)
+ )
}
override val licensesRes: Triple?
@@ -131,11 +139,17 @@ class AboutFragment : BaseFragment(R.layout.fragment_about
chooserTitle = getString(R.string.about_feedback),
email = "wulkanowyinc@gmail.com",
subject = "Zgłoszenie błędu",
- body = getString(R.string.about_feedback_template,
- "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName
+ body = getString(
+ R.string.about_feedback_template,
+ "${appInfo.systemManufacturer} ${appInfo.systemModel}",
+ appInfo.systemVersion.toString(),
+ "${appInfo.versionName}-${appInfo.buildFlavor}"
),
onActivityNotFound = {
- requireContext().openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage)
+ requireContext().openInternetBrowser(
+ "https://github.com/wulkanowy/wulkanowy/issues",
+ ::showMessage
+ )
}
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt
index 7ecb4139..342fd2d4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt
@@ -8,16 +8,17 @@ import android.view.View.VISIBLE
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
-import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.HeaderAccountBinding
import io.github.wulkanowy.databinding.ItemAccountBinding
-import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.nickOrName
import javax.inject.Inject
class AccountAdapter @Inject constructor() : RecyclerView.Adapter() {
+ var isAccountQuickDialogMode = false
+
var items = emptyList>()
var onClickListener: (StudentWithSemesters) -> Unit = {}
@@ -30,54 +31,69 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter HeaderViewHolder(HeaderAccountBinding.inflate(inflater, parent, false))
- AccountItem.ViewType.ITEM.id -> ItemViewHolder(ItemAccountBinding.inflate(inflater, parent, false))
+ AccountItem.ViewType.HEADER.id -> HeaderViewHolder(
+ HeaderAccountBinding.inflate(inflater, parent, false)
+ )
+ AccountItem.ViewType.ITEM.id -> ItemViewHolder(
+ ItemAccountBinding.inflate(inflater, parent, false)
+ )
else -> throw IllegalStateException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
- is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as Account)
- is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as StudentWithSemesters)
+ is HeaderViewHolder -> bindHeaderViewHolder(
+ holder.binding,
+ items[position].value as Account,
+ position
+ )
+ is ItemViewHolder -> bindItemViewHolder(
+ holder.binding,
+ items[position].value as StudentWithSemesters
+ )
}
}
- private fun bindHeaderViewHolder(binding: HeaderAccountBinding, account: Account) {
+ private fun bindHeaderViewHolder(
+ binding: HeaderAccountBinding,
+ account: Account,
+ position: Int
+ ) {
with(binding) {
+ accountHeaderDivider.visibility = if (position == 0) GONE else VISIBLE
accountHeaderEmail.text = account.email
accountHeaderType.setText(if (account.isParent) R.string.account_type_parent else R.string.account_type_student)
}
}
@SuppressLint("SetTextI18n")
- private fun bindItemViewHolder(binding: ItemAccountBinding, studentWithSemesters: StudentWithSemesters) {
+ private fun bindItemViewHolder(
+ binding: ItemAccountBinding,
+ studentWithSemesters: StudentWithSemesters
+ ) {
val student = studentWithSemesters.student
val semesters = studentWithSemesters.semesters
val diary = semesters.maxByOrNull { it.semesterId }
+ val isDuplicatedStudent = items.filter {
+ if (it.value !is StudentWithSemesters) return@filter false
+ val studentToCompare = it.value.student
+
+ studentToCompare.studentId == student.studentId
+ && studentToCompare.schoolSymbol == student.schoolSymbol
+ && studentToCompare.symbol == student.symbol
+ }.size > 1 && isAccountQuickDialogMode
with(binding) {
- accountItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}"
+ accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}"
accountItemSchool.text = studentWithSemesters.student.schoolName
- with(accountItemLoginMode) {
- visibility = when (Sdk.Mode.valueOf(student.loginMode)) {
- Sdk.Mode.API -> {
- setText(R.string.account_login_mobile_api)
- VISIBLE
- }
- Sdk.Mode.HYBRID -> {
- setText(R.string.account_login_hybrid)
- VISIBLE
- }
- Sdk.Mode.SCRAPPER -> {
- GONE
- }
- }
- }
+ accountItemAccountType.setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
+ accountItemAccountType.visibility = if (isDuplicatedStudent) VISIBLE else GONE
with(accountItemImage) {
- val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
- else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
+ val colorImage =
+ if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
+ else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt
deleted file mode 100644
index 1fa87268..00000000
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountDialog.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package io.github.wulkanowy.ui.modules.account
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Toast
-import android.widget.Toast.LENGTH_LONG
-import androidx.appcompat.app.AlertDialog
-import androidx.recyclerview.widget.LinearLayoutManager
-import dagger.hilt.android.AndroidEntryPoint
-import io.github.wulkanowy.R
-import io.github.wulkanowy.databinding.DialogAccountBinding
-import io.github.wulkanowy.ui.base.BaseDialogFragment
-import io.github.wulkanowy.ui.modules.login.LoginActivity
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class AccountDialog : BaseDialogFragment(), AccountView {
-
- @Inject
- lateinit var presenter: AccountPresenter
-
- @Inject
- lateinit var accountAdapter: AccountAdapter
-
- companion object {
- fun newInstance() = AccountDialog()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setStyle(STYLE_NO_TITLE, 0)
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return DialogAccountBinding.inflate(inflater).apply { binding = this }.root
- }
-
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
- presenter.onAttachView(this)
- }
-
- override fun initView() {
- accountAdapter.onClickListener = presenter::onItemSelected
-
- with(binding) {
- accountDialogAdd.setOnClickListener { presenter.onAddSelected() }
- accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() }
- accountDialogRecycler.apply {
- layoutManager = LinearLayoutManager(context)
- adapter = accountAdapter
- }
- }
- }
-
- override fun updateData(data: List>) {
- with(accountAdapter) {
- items = data
- notifyDataSetChanged()
- }
- }
-
- override fun showError(text: String, error: Throwable) {
- showMessage(text)
- }
-
- override fun showMessage(text: String) {
- Toast.makeText(context, text, LENGTH_LONG).show()
- }
-
- override fun dismissView() {
- dismiss()
- }
-
- override fun openLoginView() {
- activity?.let {
- startActivity(LoginActivity.getStartIntent(it))
- }
- }
-
- override fun showConfirmDialog() {
- context?.let {
- AlertDialog.Builder(it)
- .setTitle(R.string.account_logout_student)
- .setMessage(R.string.account_confirm)
- .setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() }
- .setNegativeButton(android.R.string.cancel) { _, _ -> }
- .show()
- }
- }
-
- override fun recreateMainView() {
- activity?.recreate()
- }
-
- override fun onDestroy() {
- presenter.onDetachView()
- super.onDestroy()
- }
-}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt
new file mode 100644
index 00000000..7293527e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt
@@ -0,0 +1,111 @@
+package io.github.wulkanowy.ui.modules.account
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.View
+import androidx.core.view.get
+import androidx.recyclerview.widget.LinearLayoutManager
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.databinding.FragmentAccountBinding
+import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
+import io.github.wulkanowy.ui.modules.login.LoginActivity
+import io.github.wulkanowy.ui.modules.main.MainActivity
+import io.github.wulkanowy.ui.modules.main.MainView
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class AccountFragment : BaseFragment(R.layout.fragment_account),
+ AccountView, MainView.TitledView {
+
+ @Inject
+ lateinit var presenter: AccountPresenter
+
+ @Inject
+ lateinit var accountAdapter: AccountAdapter
+
+ companion object {
+
+ fun newInstance() = AccountFragment()
+ }
+
+ override val titleStringId = R.string.account_title
+
+ override var subtitleString = ""
+
+ override val isViewEmpty get() = accountAdapter.items.isEmpty()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding = FragmentAccountBinding.bind(view)
+ presenter.onAttachView(this)
+ }
+
+ override fun initView() {
+ binding.accountErrorRetry.setOnClickListener { presenter.onRetry() }
+ binding.accountErrorDetails.setOnClickListener { presenter.onDetailsClick() }
+
+ binding.accountRecycler.apply {
+ layoutManager = LinearLayoutManager(context)
+ adapter = accountAdapter
+ }
+
+ accountAdapter.onClickListener = presenter::onItemSelected
+
+ with(binding) {
+ accountAdd.setOnClickListener { presenter.onAddSelected() }
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ menu[0].isVisible = false
+ }
+
+ override fun updateData(data: List>) {
+ with(accountAdapter) {
+ items = data
+ notifyDataSetChanged()
+ }
+ }
+
+ override fun openLoginView() {
+ activity?.let {
+ startActivity(LoginActivity.getStartIntent(it))
+ }
+ }
+
+ override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) {
+ (activity as? MainActivity)?.pushView(
+ AccountDetailsFragment.newInstance(
+ studentWithSemesters
+ )
+ )
+ }
+
+ override fun showErrorView(show: Boolean) {
+ binding.accountError.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
+ override fun setErrorDetails(message: String) {
+ binding.accountErrorMessage.text = message
+ }
+
+ override fun showProgress(show: Boolean) {
+ binding.accountProgress.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
+ override fun showContent(show: Boolean) {
+ with(binding) {
+ accountRecycler.visibility = if (show) View.VISIBLE else View.GONE
+ accountAdd.visibility = if (show) View.VISIBLE else View.GONE
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt
index be01f7c5..366793b6 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt
@@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.account
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.StudentRepository
-import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.afterLoading
@@ -15,101 +14,91 @@ import javax.inject.Inject
class AccountPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
- private val syncManager: SyncManager
) : BasePresenter(errorHandler, studentRepository) {
+ private lateinit var lastError: Throwable
+
override fun onAttachView(view: AccountView) {
super.onAttachView(view)
view.initView()
- Timber.i("Account dialog view was initialized")
+ Timber.i("Account view was initialized")
+ errorHandler.showErrorMessage = ::showErrorViewOnError
loadData()
}
+ fun onRetry() {
+ view?.run {
+ showErrorView(false)
+ showProgress(true)
+ }
+ loadData()
+ }
+
+ fun onDetailsClick() {
+ view?.showErrorDetailsDialog(lastError)
+ }
+
fun onAddSelected() {
Timber.i("Select add account")
view?.openLoginView()
}
- fun onRemoveSelected() {
- Timber.i("Select remove account")
- view?.showConfirmDialog()
- }
-
- fun onLogoutConfirm() {
- flowWithResource {
- val student = studentRepository.getCurrentStudent(false)
- studentRepository.logoutStudent(student)
-
- val students = studentRepository.getSavedStudents(false)
- if (students.isNotEmpty()) {
- studentRepository.switchStudent(students[0])
- }
- students
- }.onEach {
- when (it.status) {
- Status.LOADING -> Timber.i("Attempt to logout current user ")
- Status.SUCCESS -> view?.run {
- if (it.data!!.isEmpty()) {
- Timber.i("Logout result: Open login view")
- syncManager.stopSyncWorker()
- openClearLoginView()
- } else {
- Timber.i("Logout result: Switch to another student")
- recreateMainView()
- }
- }
- Status.ERROR -> {
- Timber.i("Logout result: An exception occurred")
- errorHandler.dispatch(it.error!!)
- }
- }
- }.afterLoading {
- view?.dismissView()
- }.launch("logout")
- }
-
fun onItemSelected(studentWithSemesters: StudentWithSemesters) {
- Timber.i("Select student item ${studentWithSemesters.student.id}")
- if (studentWithSemesters.student.isCurrent) {
- view?.dismissView()
- } else flowWithResource { studentRepository.switchStudent(studentWithSemesters) }.onEach {
- when (it.status) {
- Status.LOADING -> Timber.i("Attempt to change a student")
- Status.SUCCESS -> {
- Timber.i("Change a student result: Success")
- view?.recreateMainView()
- }
- Status.ERROR -> {
- Timber.i("Change a student result: An exception occurred")
- errorHandler.dispatch(it.error!!)
- }
- }
- }.afterLoading {
- view?.dismissView()
- }.launch("switch")
+ view?.openAccountDetailsView(studentWithSemesters)
}
private fun createAccountItems(items: List): List> {
- return items.groupBy { Account(it.student.email, it.student.isParent) }.map { (account, students) ->
- listOf(AccountItem(account, AccountItem.ViewType.HEADER)) + students.map { student ->
- AccountItem(student, AccountItem.ViewType.ITEM)
+ return items.groupBy {
+ Account("${it.student.userName} (${it.student.email})", it.student.isParent)
+ }
+ .map { (account, students) ->
+ listOf(
+ AccountItem(account, AccountItem.ViewType.HEADER)
+ ) + students.map { student ->
+ AccountItem(student, AccountItem.ViewType.ITEM)
+ }
}
- }.flatten()
+ .flatten()
}
private fun loadData() {
- flowWithResource { studentRepository.getSavedStudents(false) }.onEach {
- when (it.status) {
- Status.LOADING -> Timber.i("Loading account data started")
- Status.SUCCESS -> {
- Timber.i("Loading account result: Success")
- view?.updateData(createAccountItems(it.data!!))
- }
- Status.ERROR -> {
- Timber.i("Loading account result: An exception occurred")
- errorHandler.dispatch(it.error!!)
+ flowWithResource { studentRepository.getSavedStudents(false) }
+ .onEach {
+ when (it.status) {
+ Status.LOADING -> {
+ Timber.i("Loading account data started")
+ view?.run {
+ showProgress(true)
+ showContent(false)
+ }
+ }
+ Status.SUCCESS -> {
+ Timber.i("Loading account result: Success")
+ view?.updateData(createAccountItems(it.data!!))
+ view?.run {
+ showContent(true)
+ showErrorView(false)
+ }
+ }
+ Status.ERROR -> {
+ Timber.i("Loading account result: An exception occurred")
+ errorHandler.dispatch(it.error!!)
+ }
}
}
- }.launch()
+ .afterLoading { view?.showProgress(false) }
+ .launch()
+ }
+
+ private fun showErrorViewOnError(message: String, error: Throwable) {
+ view?.run {
+ if (isViewEmpty) {
+ lastError = error
+ setErrorDetails(message)
+ showErrorView(true)
+ showContent(false)
+ showProgress(false)
+ } else showError(message, error)
+ }
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt
index a1f8086c..3453909d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt
@@ -1,19 +1,26 @@
package io.github.wulkanowy.ui.modules.account
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.ui.base.BaseView
interface AccountView : BaseView {
+ val isViewEmpty: Boolean
+
fun initView()
fun updateData(data: List>)
- fun dismissView()
-
- fun showConfirmDialog()
-
fun openLoginView()
- fun recreateMainView()
+ fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters)
+
+ fun showErrorView(show: Boolean)
+
+ fun setErrorDetails(message: String)
+
+ fun showProgress(show: Boolean)
+
+ fun showContent(show: Boolean)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt
new file mode 100644
index 00000000..f4b2c833
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt
@@ -0,0 +1,157 @@
+package io.github.wulkanowy.ui.modules.account.accountdetails
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import androidx.appcompat.app.AlertDialog
+import androidx.core.view.get
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.databinding.FragmentAccountDetailsBinding
+import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.account.accountedit.AccountEditDialog
+import io.github.wulkanowy.ui.modules.main.MainActivity
+import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
+import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
+import io.github.wulkanowy.utils.nickOrName
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class AccountDetailsFragment :
+ BaseFragment(R.layout.fragment_account_details),
+ AccountDetailsView, MainView.TitledView {
+
+ @Inject
+ lateinit var presenter: AccountDetailsPresenter
+
+ override val titleStringId = R.string.account_details_title
+
+ override var subtitleString = ""
+
+ companion object {
+
+ private const val ARGUMENT_KEY = "Data"
+
+ fun newInstance(studentWithSemesters: StudentWithSemesters) =
+ AccountDetailsFragment().apply {
+ arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, studentWithSemesters) }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding = FragmentAccountDetailsBinding.bind(view)
+ presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as StudentWithSemesters)
+ }
+
+ override fun initView() {
+ binding.accountDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
+ binding.accountDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
+ binding.accountDetailsLogout.setOnClickListener { presenter.onRemoveSelected() }
+ binding.accountDetailsSelect.setOnClickListener { presenter.onStudentSelect() }
+
+ binding.accountDetailsPersonalData.setOnClickListener {
+ presenter.onStudentInfoSelected(StudentInfoView.Type.PERSONAL)
+ }
+ binding.accountDetailsAddressData.setOnClickListener {
+ presenter.onStudentInfoSelected(StudentInfoView.Type.ADDRESS)
+ }
+ binding.accountDetailsContactData.setOnClickListener {
+ presenter.onStudentInfoSelected(StudentInfoView.Type.CONTACT)
+ }
+ binding.accountDetailsFamilyData.setOnClickListener {
+ presenter.onStudentInfoSelected(StudentInfoView.Type.FAMILY)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ menu[0].isVisible = false
+ inflater.inflate(R.menu.action_menu_account_details, menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return if (item.itemId == R.id.accountDetailsMenuEdit) {
+ presenter.onAccountEditSelected()
+ true
+ } else false
+ }
+
+ override fun showAccountData(student: Student) {
+ with(binding) {
+ accountDetailsName.text = student.nickOrName
+ accountDetailsSchool.text = student.schoolName
+ }
+ }
+
+ override fun enableSelectStudentButton(enable: Boolean) {
+ binding.accountDetailsSelect.isEnabled = enable
+ }
+
+ override fun showAccountEditDetailsDialog(student: Student) {
+ (requireActivity() as MainActivity).showDialogFragment(
+ AccountEditDialog.newInstance(student)
+ )
+ }
+
+ override fun showLogoutConfirmDialog() {
+ context?.let {
+ AlertDialog.Builder(it)
+ .setTitle(R.string.account_logout_student)
+ .setMessage(R.string.account_confirm)
+ .setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() }
+ .setNegativeButton(android.R.string.cancel) { _, _ -> }
+ .show()
+ }
+ }
+
+ override fun popView() {
+ (requireActivity() as MainActivity).popView(2)
+ }
+
+ override fun recreateMainView() {
+ requireActivity().recreate()
+ }
+
+ override fun openStudentInfoView(
+ infoType: StudentInfoView.Type,
+ studentWithSemesters: StudentWithSemesters
+ ) {
+ (requireActivity() as MainActivity).pushView(
+ StudentInfoFragment.newInstance(
+ infoType,
+ studentWithSemesters
+ )
+ )
+ }
+
+ override fun showErrorView(show: Boolean) {
+ binding.accountDetailsError.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
+ override fun setErrorDetails(message: String) {
+ binding.accountDetailsErrorMessage.text = message
+ }
+
+ override fun showProgress(show: Boolean) {
+ binding.accountDetailsProgress.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
+ override fun showContent(show: Boolean) {
+ binding.accountDetailsContent.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
+ override fun onDestroyView() {
+ presenter.onDetachView()
+ super.onDestroyView()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt
new file mode 100644
index 00000000..7b93d3d8
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt
@@ -0,0 +1,172 @@
+package io.github.wulkanowy.ui.modules.account.accountdetails
+
+import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.Status
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.services.sync.SyncManager
+import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.ui.base.ErrorHandler
+import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
+import io.github.wulkanowy.utils.afterLoading
+import io.github.wulkanowy.utils.flowWithResource
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import timber.log.Timber
+import javax.inject.Inject
+
+class AccountDetailsPresenter @Inject constructor(
+ errorHandler: ErrorHandler,
+ studentRepository: StudentRepository,
+ private val syncManager: SyncManager
+) : BasePresenter(errorHandler, studentRepository) {
+
+ private lateinit var studentWithSemesters: StudentWithSemesters
+
+ private lateinit var lastError: Throwable
+
+ private var studentId: Long? = null
+
+ fun onAttachView(view: AccountDetailsView, studentWithSemesters: StudentWithSemesters) {
+ super.onAttachView(view)
+ studentId = studentWithSemesters.student.id
+
+ view.initView()
+ errorHandler.showErrorMessage = ::showErrorViewOnError
+ Timber.i("Account details view was initialized")
+ loadData()
+ }
+
+ fun onRetry() {
+ view?.run {
+ showErrorView(false)
+ showProgress(true)
+ }
+ loadData()
+ }
+
+ fun onDetailsClick() {
+ view?.showErrorDetailsDialog(lastError)
+ }
+
+ private fun loadData() {
+ flowWithResource { studentRepository.getSavedStudents() }
+ .map { studentWithSemesters ->
+ Resource(
+ data = studentWithSemesters.data?.single { it.student.id == studentId },
+ status = studentWithSemesters.status,
+ error = studentWithSemesters.error
+ )
+ }
+ .onEach {
+ when (it.status) {
+ Status.LOADING -> {
+ view?.run {
+ showProgress(true)
+ showContent(false)
+ }
+ Timber.i("Loading account details view started")
+ }
+ Status.SUCCESS -> {
+ Timber.i("Loading account details view result: Success")
+ studentWithSemesters = it.data!!
+ view?.run {
+ showAccountData(studentWithSemesters.student)
+ enableSelectStudentButton(!studentWithSemesters.student.isCurrent)
+ showContent(true)
+ showErrorView(false)
+ }
+ }
+ Status.ERROR -> {
+ Timber.i("Loading account details view result: An exception occurred")
+ errorHandler.dispatch(it.error!!)
+ }
+ }
+ }
+ .afterLoading { view?.showProgress(false) }
+ .launch()
+ }
+
+ fun onAccountEditSelected() {
+ view?.showAccountEditDetailsDialog(studentWithSemesters.student)
+ }
+
+ fun onStudentInfoSelected(infoType: StudentInfoView.Type) {
+ view?.openStudentInfoView(infoType, studentWithSemesters)
+ }
+
+ fun onStudentSelect() {
+ Timber.i("Select student ${studentWithSemesters.student.id}")
+
+ flowWithResource { studentRepository.switchStudent(studentWithSemesters) }
+ .onEach {
+ when (it.status) {
+ Status.LOADING -> Timber.i("Attempt to change a student")
+ Status.SUCCESS -> {
+ Timber.i("Change a student result: Success")
+ view?.recreateMainView()
+ }
+ Status.ERROR -> {
+ Timber.i("Change a student result: An exception occurred")
+ errorHandler.dispatch(it.error!!)
+ }
+ }
+ }.afterLoading {
+ view?.popView()
+ }.launch("switch")
+ }
+
+ fun onRemoveSelected() {
+ Timber.i("Select remove account")
+ view?.showLogoutConfirmDialog()
+ }
+
+ fun onLogoutConfirm() {
+ flowWithResource {
+ val studentToLogout = studentWithSemesters.student
+
+ studentRepository.logoutStudent(studentToLogout)
+ val students = studentRepository.getSavedStudents(false)
+
+ if (studentToLogout.isCurrent && students.isNotEmpty()) {
+ studentRepository.switchStudent(students[0])
+ }
+
+ return@flowWithResource students
+ }.onEach {
+ when (it.status) {
+ Status.LOADING -> Timber.i("Attempt to logout user")
+ Status.SUCCESS -> view?.run {
+ when {
+ it.data!!.isEmpty() -> {
+ Timber.i("Logout result: Open login view")
+ syncManager.stopSyncWorker()
+ openClearLoginView()
+ }
+ studentWithSemesters.student.isCurrent -> {
+ Timber.i("Logout result: Logout student and switch to another")
+ recreateMainView()
+ }
+ else -> Timber.i("Logout result: Logout student")
+ }
+ }
+ Status.ERROR -> {
+ Timber.i("Logout result: An exception occurred")
+ errorHandler.dispatch(it.error!!)
+ }
+ }
+ }.afterLoading {
+ view?.popView()
+ }.launch("logout")
+ }
+
+ private fun showErrorViewOnError(message: String, error: Throwable) {
+ view?.run {
+ lastError = error
+ setErrorDetails(message)
+ showErrorView(true)
+ showContent(false)
+ showProgress(false)
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt
new file mode 100644
index 00000000..652f0c1a
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt
@@ -0,0 +1,36 @@
+package io.github.wulkanowy.ui.modules.account.accountdetails
+
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.ui.base.BaseView
+import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
+
+interface AccountDetailsView : BaseView {
+
+ fun initView()
+
+ fun showAccountData(student: Student)
+
+ fun showAccountEditDetailsDialog(student: Student)
+
+ fun showLogoutConfirmDialog()
+
+ fun popView()
+
+ fun recreateMainView()
+
+ fun enableSelectStudentButton(enable: Boolean)
+
+ fun openStudentInfoView(
+ infoType: StudentInfoView.Type,
+ studentWithSemesters: StudentWithSemesters
+ )
+
+ fun showErrorView(show: Boolean)
+
+ fun setErrorDetails(message: String)
+
+ fun showProgress(show: Boolean)
+
+ fun showContent(show: Boolean)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt
new file mode 100644
index 00000000..89f23e29
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt
@@ -0,0 +1,72 @@
+package io.github.wulkanowy.ui.modules.account.accountedit
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.databinding.DialogAccountEditBinding
+import io.github.wulkanowy.ui.base.BaseDialogFragment
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class AccountEditDialog : BaseDialogFragment(), AccountEditView {
+
+ @Inject
+ lateinit var presenter: AccountEditPresenter
+
+ companion object {
+
+ private const val ARGUMENT_KEY = "student_with_semesters"
+
+ fun newInstance(student: Student) =
+ AccountEditDialog().apply {
+ arguments = Bundle().apply {
+ putSerializable(ARGUMENT_KEY, student)
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setStyle(STYLE_NO_TITLE, 0)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View = DialogAccountEditBinding.inflate(inflater).apply { binding = this }.root
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
+ }
+
+ override fun initView() {
+ with(binding) {
+ accountEditDetailsCancel.setOnClickListener { dismiss() }
+ accountEditDetailsSave.setOnClickListener {
+ presenter.changeStudentNick(binding.accountEditDetailsNickText.text.toString())
+ }
+ }
+ }
+
+ override fun showCurrentNick(nick: String) {
+ binding.accountEditDetailsNickText.setText(nick)
+ }
+
+ override fun popView() {
+ dismiss()
+ }
+
+ override fun recreateMainView() {
+ activity?.recreate()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ presenter.onDetachView()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt
new file mode 100644
index 00000000..7830605c
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt
@@ -0,0 +1,54 @@
+package io.github.wulkanowy.ui.modules.account.accountedit
+
+import io.github.wulkanowy.data.Status
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.StudentNick
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.ui.base.ErrorHandler
+import io.github.wulkanowy.utils.afterLoading
+import io.github.wulkanowy.utils.flowWithResource
+import kotlinx.coroutines.flow.onEach
+import timber.log.Timber
+import javax.inject.Inject
+
+class AccountEditPresenter @Inject constructor(
+ errorHandler: ErrorHandler,
+ studentRepository: StudentRepository
+) : BasePresenter(errorHandler, studentRepository) {
+
+ lateinit var student: Student
+
+ fun onAttachView(view: AccountEditView, student: Student) {
+ super.onAttachView(view)
+ this.student = student
+
+ with(view) {
+ initView()
+ showCurrentNick(student.nick.trim())
+ }
+ Timber.i("Account edit dialog view was initialized")
+ }
+
+ fun changeStudentNick(nick: String) {
+ flowWithResource {
+ val studentNick =
+ StudentNick(nick = nick.trim()).apply { id = student.id }
+ studentRepository.updateStudentNick(studentNick)
+ }.onEach {
+ when (it.status) {
+ Status.LOADING -> Timber.i("Attempt to change a student nick")
+ Status.SUCCESS -> {
+ Timber.i("Change a student nick result: Success")
+ view?.recreateMainView()
+ }
+ Status.ERROR -> {
+ Timber.i("Change a student result: An exception occurred")
+ errorHandler.dispatch(it.error!!)
+ }
+ }
+ }
+ .afterLoading { view?.popView() }
+ .launch()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt
new file mode 100644
index 00000000..b25eec6c
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt
@@ -0,0 +1,14 @@
+package io.github.wulkanowy.ui.modules.account.accountedit
+
+import io.github.wulkanowy.ui.base.BaseView
+
+interface AccountEditView : BaseView {
+
+ fun initView()
+
+ fun popView()
+
+ fun recreateMainView()
+
+ fun showCurrentNick(nick: String)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt
new file mode 100644
index 00000000..cb64a8fd
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt
@@ -0,0 +1,82 @@
+package io.github.wulkanowy.ui.modules.account.accountquick
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.databinding.DialogAccountQuickBinding
+import io.github.wulkanowy.ui.base.BaseDialogFragment
+import io.github.wulkanowy.ui.modules.account.AccountAdapter
+import io.github.wulkanowy.ui.modules.account.AccountFragment
+import io.github.wulkanowy.ui.modules.account.AccountItem
+import io.github.wulkanowy.ui.modules.main.MainActivity
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class AccountQuickDialog : BaseDialogFragment(), AccountQuickView {
+
+ @Inject
+ lateinit var accountAdapter: AccountAdapter
+
+ @Inject
+ lateinit var presenter: AccountQuickPresenter
+
+ companion object {
+ fun newInstance() = AccountQuickDialog()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setStyle(STYLE_NO_TITLE, 0)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ presenter.onAttachView(this)
+ }
+
+ override fun initView() {
+ binding.accountQuickDialogManger.setOnClickListener { presenter.onManagerSelected() }
+
+ with(accountAdapter) {
+ isAccountQuickDialogMode = true
+ onClickListener = presenter::onStudentSelect
+ }
+
+ with(binding.accountQuickDialogRecycler) {
+ layoutManager = LinearLayoutManager(context)
+ adapter = accountAdapter
+ }
+ }
+
+ override fun updateData(data: List>) {
+ with(accountAdapter) {
+ items = data
+ notifyDataSetChanged()
+ }
+ }
+
+ override fun popView() {
+ dismiss()
+ }
+
+ override fun recreateMainView() {
+ activity?.recreate()
+ }
+
+ override fun openAccountView() {
+ (requireActivity() as MainActivity).pushView(AccountFragment.newInstance())
+ }
+
+ override fun onDestroyView() {
+ presenter.onDetachView()
+ super.onDestroyView()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt
new file mode 100644
index 00000000..43cc8bc7
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt
@@ -0,0 +1,79 @@
+package io.github.wulkanowy.ui.modules.account.accountquick
+
+import io.github.wulkanowy.data.Status
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.ui.base.ErrorHandler
+import io.github.wulkanowy.ui.modules.account.AccountItem
+import io.github.wulkanowy.utils.afterLoading
+import io.github.wulkanowy.utils.flowWithResource
+import kotlinx.coroutines.flow.onEach
+import timber.log.Timber
+import javax.inject.Inject
+
+class AccountQuickPresenter @Inject constructor(
+ errorHandler: ErrorHandler,
+ studentRepository: StudentRepository
+) : BasePresenter(errorHandler, studentRepository) {
+
+ override fun onAttachView(view: AccountQuickView) {
+ super.onAttachView(view)
+ view.initView()
+ Timber.i("Account quick dialog view was initialized")
+ loadData()
+ }
+
+ fun onManagerSelected() {
+ view?.run {
+ openAccountView()
+ popView()
+ }
+ }
+
+ fun onStudentSelect(studentWithSemesters: StudentWithSemesters) {
+ Timber.i("Select student ${studentWithSemesters.student.id}")
+
+ if (studentWithSemesters.student.isCurrent) {
+ view?.popView()
+ return
+ }
+
+ flowWithResource { studentRepository.switchStudent(studentWithSemesters) }
+ .onEach {
+ when (it.status) {
+ Status.LOADING -> Timber.i("Attempt to change a student")
+ Status.SUCCESS -> {
+ Timber.i("Change a student result: Success")
+ view?.recreateMainView()
+ }
+ Status.ERROR -> {
+ Timber.i("Change a student result: An exception occurred")
+ errorHandler.dispatch(it.error!!)
+ }
+ }
+ }
+ .afterLoading { view?.popView() }
+ .launch("switch")
+ }
+
+ private fun loadData() {
+ flowWithResource { studentRepository.getSavedStudents(false) }.onEach {
+ when (it.status) {
+ Status.LOADING -> Timber.i("Loading account data started")
+ Status.SUCCESS -> {
+ Timber.i("Loading account result: Success")
+ view?.updateData(createAccountItems(it.data!!))
+ }
+ Status.ERROR -> {
+ Timber.i("Loading account result: An exception occurred")
+ errorHandler.dispatch(it.error!!)
+ }
+ }
+ }.launch()
+ }
+
+ private fun createAccountItems(items: List) = items.map {
+ AccountItem(it, AccountItem.ViewType.ITEM)
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickView.kt
new file mode 100644
index 00000000..4a9420d9
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickView.kt
@@ -0,0 +1,17 @@
+package io.github.wulkanowy.ui.modules.account.accountquick
+
+import io.github.wulkanowy.ui.base.BaseView
+import io.github.wulkanowy.ui.modules.account.AccountItem
+
+interface AccountQuickView : BaseView {
+
+ fun initView()
+
+ fun updateData(data: List>)
+
+ fun recreateMainView()
+
+ fun popView()
+
+ fun openAccountView()
+}
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 a106bc50..00d5aae8 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
@@ -26,6 +26,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.getThemeAttrColor
import java.time.LocalDate
import javax.inject.Inject
@@ -60,6 +61,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag
override val excuseActionMode: Boolean get() = attendanceAdapter.excuseActionMode
private var actionMode: ActionMode? = null
+
private val actionModeCallback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
val inflater = mode.menuInflater
@@ -111,6 +113,8 @@ class AttendanceFragment : BaseFragment(R.layout.frag
with(binding) {
attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ attendanceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ attendanceSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
attendanceErrorRetry.setOnClickListener { presenter.onRetry() }
attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
@@ -222,6 +226,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag
setDateRangeLimiter(SchooldaysRangeLimiter())
version = DatePickerDialog.Version.VERSION_2
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
+ vibrate(false)
show(this@AttendanceFragment.parentFragmentManager, null)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt
index 6b971f26..118971e6 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt
@@ -15,6 +15,7 @@ import io.github.wulkanowy.databinding.FragmentAttendanceSummaryBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.setOnItemSelectedListener
import javax.inject.Inject
@@ -56,6 +57,8 @@ class AttendanceSummaryFragment :
with(binding) {
attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ attendanceSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ attendanceSummarySwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
attendanceSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt
index 6173c15b..dd10a65e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt
@@ -10,6 +10,7 @@ import io.github.wulkanowy.databinding.FragmentConferenceBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
+import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
@@ -47,7 +48,9 @@ class ConferenceFragment : BaseFragment(R.layout.frag
}
with(binding) {
- conferenceSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
+ conferenceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ conferenceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ conferenceSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
conferenceErrorRetry.setOnClickListener { presenter.onRetry() }
conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt
index 9cc1aeda..0940b0bd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt
@@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
@@ -55,6 +56,8 @@ class ExamFragment : BaseFragment(R.layout.fragment_exam),
with(binding) {
examSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ examSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ examSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
examErrorRetry.setOnClickListener { presenter.onRetry() }
examErrorDetails.setOnClickListener { presenter.onDetailsClick() }
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 0bd971ec..36c3c4f8 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
@@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.Resource
+import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
@@ -33,81 +34,162 @@ class GradeAverageProvider @Inject constructor(
private val minusModifier get() = preferencesRepository.gradeMinusModifier
- fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) = flowWithResourceIn {
- val semesters = semesterRepository.getSemesters(student)
+ fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) =
+ flowWithResourceIn {
+ val semesters = semesterRepository.getSemesters(student)
- when (preferencesRepository.gradeAverageMode) {
- ONE_SEMESTER -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh)
- BOTH_SEMESTERS -> calculateBothSemestersAverage(student, semesters, semesterId, forceRefresh)
- ALL_YEAR -> calculateAllYearAverage(student, semesters, semesterId, forceRefresh)
- }
- }.distinctUntilChanged()
-
- private fun calculateBothSemestersAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Flow>> {
- val selectedSemester = semesters.single { it.semesterId == semesterId }
- val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
-
- val selectedSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh)
-
- return if (selectedSemester == firstSemester) selectedSemesterDetailsWithAverage else {
- val firstSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, firstSemester, forceRefresh)
- selectedSemesterDetailsWithAverage.combine(firstSemesterDetailsWithAverage) { selectedDetails, secondDetails ->
- val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 }
- secondDetails.copy(data = selectedDetails.data?.map { selected ->
- val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject }
- selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
- val selectedGrades = selected.grades.updateModifiers(student).calcAverage()
- (selectedGrades + (second?.grades?.updateModifiers(student)?.calcAverage() ?: selectedGrades)) / 2
- } else (selected.average + (second?.average ?: selected.average)) / 2)
- })
- }
- }
- }
-
- private fun calculateAllYearAverage(student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean): Flow>> {
- val selectedSemester = semesters.single { it.semesterId == semesterId }
- val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
-
- val selectedSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh)
-
- return if (selectedSemester == firstSemester) selectedSemesterDetailsWithAverage else {
- val firstSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, firstSemester, forceRefresh)
- selectedSemesterDetailsWithAverage.combine(firstSemesterDetailsWithAverage) { selectedDetails, secondDetails ->
- val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 }
- secondDetails.copy(data = selectedDetails.data?.map { selected ->
- val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject }
- selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
- (selected.grades.updateModifiers(student) + second?.grades?.updateModifiers(student).orEmpty()).calcAverage()
- } else selected.average)
- })
- }
- }
- }
-
- private fun getSemesterDetailsWithAverage(student: Student, semester: Semester, forceRefresh: Boolean): Flow>> {
- return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh).map { res ->
- val (details, summaries) = res.data ?: null to null
- val isAnyAverage = summaries.orEmpty().any { it.average != .0 }
- val allGrades = details.orEmpty().groupBy { it.subject }
-
- val items = summaries?.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage)?.map { summary ->
- val grades = allGrades[summary.subject].orEmpty()
- GradeDetailsWithAverage(
- subject = summary.subject,
- average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
- grades.updateModifiers(student).calcAverage()
- } else summary.average,
- points = summary.pointsSum,
- summary = summary,
- grades = grades
+ when (preferencesRepository.gradeAverageMode) {
+ ONE_SEMESTER -> getGradeSubjects(
+ student = student,
+ semester = semesters.single { it.semesterId == semesterId },
+ forceRefresh = forceRefresh
+ )
+ BOTH_SEMESTERS -> calculateCombinedAverage(
+ student = student,
+ semesters = semesters,
+ semesterId = semesterId,
+ forceRefresh = forceRefresh,
+ averageMode = BOTH_SEMESTERS
+ )
+ ALL_YEAR -> calculateCombinedAverage(
+ student = student,
+ semesters = semesters,
+ semesterId = semesterId,
+ forceRefresh = forceRefresh,
+ averageMode = ALL_YEAR
)
}
+ }.distinctUntilChanged()
- Resource(res.status, items, res.error)
+ private fun calculateCombinedAverage(
+ student: Student,
+ semesters: List,
+ semesterId: Int,
+ forceRefresh: Boolean,
+ averageMode: GradeAverageMode
+ ): Flow>> {
+ val gradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
+ val selectedSemester = semesters.single { it.semesterId == semesterId }
+ val firstSemester =
+ semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
+
+ val selectedSemesterGradeSubjects =
+ getGradeSubjects(student, selectedSemester, forceRefresh)
+
+ if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects
+
+ val firstSemesterGradeSubjects =
+ getGradeSubjects(student, firstSemester, forceRefresh)
+
+ return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject ->
+ if (firstSemesterGradeSubject.status == Status.ERROR) {
+ return@combine firstSemesterGradeSubject
+ }
+
+ val isAnyAverage = secondSemesterGradeSubject.data.orEmpty().any { it.average != .0 }
+ val updatedData = secondSemesterGradeSubject.data?.map { secondSemesterSubject ->
+ val firstSemesterSubject = firstSemesterGradeSubject.data.orEmpty()
+ .singleOrNull { it.subject == secondSemesterSubject.subject }
+
+ val updatedAverage = if (averageMode == ALL_YEAR) {
+ calculateAllYearAverage(
+ student = student,
+ isAnyAverage = isAnyAverage,
+ gradeAverageForceCalc = gradeAverageForceCalc,
+ secondSemesterSubject = secondSemesterSubject,
+ firstSemesterSubject = firstSemesterSubject
+ )
+ } else {
+ calculateBothSemestersAverage(
+ student = student,
+ isAnyAverage = isAnyAverage,
+ gradeAverageForceCalc = gradeAverageForceCalc,
+ secondSemesterSubject = secondSemesterSubject,
+ firstSemesterSubject = firstSemesterSubject
+ )
+ }
+ secondSemesterSubject.copy(average = updatedAverage)
+ }
+ secondSemesterGradeSubject.copy(data = updatedData)
}
}
- private fun List.emulateEmptySummaries(student: Student, semester: Semester, grades: List>>, calcAverage: Boolean): List {
+ private fun calculateAllYearAverage(
+ student: Student,
+ isAnyAverage: Boolean,
+ gradeAverageForceCalc: Boolean,
+ secondSemesterSubject: GradeSubject,
+ firstSemesterSubject: GradeSubject?
+ ) = if (!isAnyAverage || gradeAverageForceCalc) {
+ val updatedSecondSemesterGrades =
+ secondSemesterSubject.grades.updateModifiers(student)
+ val updatedFirstSemesterGrades =
+ firstSemesterSubject?.grades?.updateModifiers(student).orEmpty()
+
+ (updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage()
+ } else {
+ secondSemesterSubject.average
+ }
+
+ private fun calculateBothSemestersAverage(
+ student: Student,
+ isAnyAverage: Boolean,
+ gradeAverageForceCalc: Boolean,
+ secondSemesterSubject: GradeSubject,
+ firstSemesterSubject: GradeSubject?
+ ) = if (!isAnyAverage || gradeAverageForceCalc) {
+ val secondSemesterAverage =
+ secondSemesterSubject.grades.updateModifiers(student).calcAverage()
+ val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
+ ?.calcAverage() ?: secondSemesterAverage
+ val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
+
+ (secondSemesterAverage + firstSemesterAverage) / divider
+ } else {
+ (secondSemesterSubject.average + (firstSemesterSubject?.average ?: secondSemesterSubject.average)) / 2
+ }
+
+ private fun getGradeSubjects(
+ student: Student,
+ semester: Semester,
+ forceRefresh: Boolean
+ ): Flow>> {
+ val gradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
+
+ return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
+ .map { res ->
+ val (details, summaries) = res.data ?: null to null
+ val isAnyAverage = summaries.orEmpty().any { it.average != .0 }
+ val allGrades = details.orEmpty().groupBy { it.subject }
+
+ val items = summaries?.emulateEmptySummaries(
+ student,
+ semester,
+ allGrades.toList(),
+ isAnyAverage
+ )?.map { summary ->
+ val grades = allGrades[summary.subject].orEmpty()
+ GradeSubject(
+ subject = summary.subject,
+ average = if (!isAnyAverage || gradeAverageForceCalc) {
+ grades.updateModifiers(student).calcAverage()
+ } else summary.average,
+ points = summary.pointsSum,
+ summary = summary,
+ grades = grades
+ )
+ }
+
+ Resource(res.status, items, res.error)
+ }
+ }
+
+ private fun List.emulateEmptySummaries(
+ student: Student,
+ semester: Semester,
+ grades: List>>,
+ calcAverage: Boolean
+ ): List {
if (isNotEmpty() && size > grades.size) return this
return grades.mapIndexed { i, (subject, details) ->
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 0678e13e..91e39e06 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
@@ -33,7 +33,6 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade
private var semesterSwitchMenu: MenuItem? = null
companion object {
- private const val SAVED_SEMESTER_KEY = "CURRENT_SEMESTER"
fun newInstance() = GradeFragment()
}
@@ -52,7 +51,7 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentGradeBinding.bind(view)
- presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SEMESTER_KEY))
+ presenter.onAttachView(this)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@@ -161,11 +160,6 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
}
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- outState.putInt(SAVED_SEMESTER_KEY, presenter.selectedIndex)
- }
-
override fun onDestroyView() {
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 d64613c0..bfc504d2 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
@@ -21,8 +21,7 @@ class GradePresenter @Inject constructor(
private val analytics: AnalyticsHelper
) : BasePresenter(errorHandler, studentRepository) {
- var selectedIndex = 0
- private set
+ private var selectedIndex = 0
private var schoolYear = 0
@@ -32,9 +31,8 @@ class GradePresenter @Inject constructor(
private lateinit var lastError: Throwable
- fun onAttachView(view: GradeView, savedIndex: Int?) {
+ override fun onAttachView(view: GradeView) {
super.onAttachView(view)
- selectedIndex = savedIndex ?: 0
view.initView()
Timber.i("Grade view was initialized with $selectedIndex index")
errorHandler.showErrorMessage = ::showErrorViewOnError
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeDetailsWithAverage.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt
similarity index 88%
rename from app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeDetailsWithAverage.kt
rename to app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt
index 3f5d706b..ee4266c5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeDetailsWithAverage.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt
@@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
-data class GradeDetailsWithAverage(
+data class GradeSubject(
val subject: String,
val average: Double,
val points: String,
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt
index ef9a932e..9d4da767 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt
@@ -17,6 +17,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.ui.modules.main.MainActivity
+import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
@@ -65,7 +66,9 @@ class GradeDetailsFragment :
layoutManager = LinearLayoutManager(context)
adapter = gradeDetailsAdapter
}
- gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
+ gradeDetailsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ gradeDetailsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ gradeDetailsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
gradeDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
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 afec6b5e..6d86c7bb 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
@@ -10,9 +10,9 @@ import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
-import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.ALPHABETIC
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.DATE
+import io.github.wulkanowy.ui.modules.grade.GradeSubject
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
@@ -201,8 +201,9 @@ class GradeDetailsPresenter @Inject constructor(
}.launch()
}
- private fun updateNewGradesAmount(grades: List) {
- newGradesAmount = grades.sumBy { item -> item.grades.sumBy { grade -> if (!grade.isRead) 1 else 0 } }
+ private fun updateNewGradesAmount(grades: List) {
+ newGradesAmount =
+ grades.sumBy { item -> item.grades.sumBy { grade -> if (!grade.isRead) 1 else 0 } }
}
private fun showErrorViewOnError(message: String, error: Throwable) {
@@ -217,7 +218,7 @@ class GradeDetailsPresenter @Inject constructor(
}
@SuppressLint("DefaultLocale")
- private fun createGradeItems(items: List): List {
+ private fun createGradeItems(items: List): List {
return items
.let { gradesWithAverages ->
if (!preferencesRepository.showSubjectsWithoutGrades) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt
index cbcb444a..0ffb4225 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt
@@ -78,18 +78,18 @@ class GradeStatisticsAdapter @Inject constructor() :
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
- is PartialViewHolder -> bindPartialChart(holder, items[position].partial!!)
- is SemesterViewHolder -> bindSemesterChart(holder, items[position].semester!!)
- is PointsViewHolder -> bindBarChart(holder, items[position].points!!)
+ is PartialViewHolder -> bindPartialChart(holder.binding, items[position].partial!!)
+ is SemesterViewHolder -> bindSemesterChart(holder.binding, items[position].semester!!)
+ is PointsViewHolder -> bindBarChart(holder.binding, items[position].points!!)
}
}
- private fun bindPartialChart(holder: PartialViewHolder, partials: GradePartialStatistics) {
- bindPieChart(holder.binding, partials.subject, partials.classAverage, partials.classAmounts)
+ private fun bindPartialChart(binding: ItemGradeStatisticsPieBinding, partials: GradePartialStatistics) {
+ bindPieChart(binding, partials.subject, partials.classAverage, partials.classAmounts)
}
- private fun bindSemesterChart(holder: SemesterViewHolder, semester: GradeSemesterStatistics) {
- bindPieChart(holder.binding, semester.subject, semester.average, semester.amounts)
+ private fun bindSemesterChart(binding: ItemGradeStatisticsPieBinding, semester: GradeSemesterStatistics) {
+ bindPieChart(binding, semester.subject, semester.average, semester.amounts)
}
private fun bindPieChart(binding: ItemGradeStatisticsPieBinding, subject: String, average: String, amounts: List) {
@@ -103,9 +103,12 @@ class GradeStatisticsAdapter @Inject constructor() :
else -> materialGradeColors
}
- val dataset = PieDataSet(amounts.mapIndexed { grade, amount ->
- PieEntry(amount.toFloat(), (grade + 1).toString())
- }.reversed().filterNot { it.value == 0f }, "Legenda")
+ val dataset = PieDataSet(
+ amounts.mapIndexed { grade, amount ->
+ PieEntry(amount.toFloat(), (grade + 1).toString())
+ }.reversed().filterNot { it.value == 0f },
+ binding.root.context.getString(R.string.grade_statistics_legend)
+ )
with(dataset) {
valueTextSize = 12f
@@ -138,11 +141,13 @@ class GradeStatisticsAdapter @Inject constructor() :
})
}
+ val numberOfGradesString = amounts.fold(0) { acc, it -> acc + it }
+ .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) }
+ val averageString = binding.root.context.getString(R.string.grade_statistics_average, average)
+
minAngleForSlices = 25f
description.isEnabled = false
- centerText = amounts.fold(0) { acc, it -> acc + it }
- .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } +
- ("\n\nŚrednia: $average").takeIf { average.isNotBlank() }.orEmpty()
+ centerText = numberOfGradesString + ("\n\n" + averageString).takeIf { average.isNotBlank() }.orEmpty()
setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground))
setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary))
@@ -150,8 +155,8 @@ class GradeStatisticsAdapter @Inject constructor() :
}
}
- private fun bindBarChart(holder: PointsViewHolder, points: GradePointsStatistics) {
- with(holder.binding.gradeStatisticsBarTitle) {
+ private fun bindBarChart(binding: ItemGradeStatisticsBarBinding, points: GradePointsStatistics) {
+ with(binding.gradeStatisticsBarTitle) {
text = points.subject
visibility = if (items.size == 1) GONE else VISIBLE
}
@@ -159,18 +164,18 @@ class GradeStatisticsAdapter @Inject constructor() :
val dataset = BarDataSet(listOf(
BarEntry(1f, points.others.toFloat()),
BarEntry(2f, points.student.toFloat())
- ), "Legenda")
+ ), binding.root.context.getString(R.string.grade_statistics_legend))
with(dataset) {
valueTextSize = 12f
- valueTextColor = holder.binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary)
+ valueTextColor = binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary)
valueFormatter = object : ValueFormatter() {
override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%"
}
colors = gradePointsColors
}
- with(holder.binding.gradeStatisticsBar) {
+ with(binding.gradeStatisticsBar) {
setTouchEnabled(false)
if (items.size == 1) animateXY(1000, 1000)
data = BarData(dataset).apply {
@@ -179,12 +184,12 @@ class GradeStatisticsAdapter @Inject constructor() :
}
legend.setCustom(listOf(
LegendEntry().apply {
- label = "Średnia klasy"
+ label = binding.root.context.getString(R.string.grade_statistics_average_class)
formColor = gradePointsColors[0]
form = Legend.LegendForm.SQUARE
},
LegendEntry().apply {
- label = "Uczeń"
+ label = binding.root.context.getString(R.string.grade_statistics_average_student)
formColor = gradePointsColors[1]
form = Legend.LegendForm.SQUARE
}
@@ -193,7 +198,7 @@ class GradeStatisticsAdapter @Inject constructor() :
description.isEnabled = false
- holder.binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary).let {
+ binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary).let {
axisLeft.textColor = it
axisRight.textColor = it
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt
index 75050249..1ce7a202 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt
@@ -13,6 +13,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.setOnItemSelectedListener
import javax.inject.Inject
@@ -69,6 +70,8 @@ class GradeStatisticsFragment :
gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ gradeStatisticsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ gradeStatisticsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() }
gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt
index 37f47869..47ea52d3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt
@@ -172,6 +172,7 @@ class GradeStatisticsPresenter @Inject constructor(
showErrorView(false)
enableSwipe(true)
showRefresh(true)
+ showProgress(false)
updateData(it.data!!, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt
index 4d12dcd5..0ac16fb3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt
@@ -13,6 +13,7 @@ import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView
+import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
@@ -52,7 +53,9 @@ class GradeSummaryFragment :
adapter = gradeSummaryAdapter
}
with(binding) {
- gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
+ gradeSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ gradeSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ gradeSummarySwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
index 17c14b85..7adfd7e5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
@@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
-import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
+import io.github.wulkanowy.ui.modules.grade.GradeSubject
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
@@ -135,14 +135,14 @@ class GradeSummaryPresenter @Inject constructor(
cancelJobs("load")
}
- private fun createGradeSummaryItems(items: List): List {
+ private fun createGradeSummaryItems(items: List): List {
return items
.filter { !checkEmpty(it) }
.sortedBy { it.subject }
.map { it.summary.copy(average = it.average) }
}
- private fun checkEmpty(gradeSummary: GradeDetailsWithAverage): Boolean {
+ private fun checkEmpty(gradeSummary: GradeSubject): Boolean {
return gradeSummary.run {
summary.finalGrade.isBlank()
&& summary.predictedGrade.isBlank()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt
index 85173e91..1d9434dc 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt
@@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
@@ -55,6 +56,8 @@ class HomeworkFragment : BaseFragment(R.layout.fragment
with(binding) {
homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ homeworkSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ homeworkSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
homeworkErrorRetry.setOnClickListener { presenter.onRetry() }
homeworkErrorDetails.setOnClickListener { presenter.onDetailsClick() }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt
index 0f1d836d..3de23585 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt
@@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.databinding.FragmentLuckyNumberBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
@@ -38,7 +39,9 @@ class LuckyNumberFragment :
override fun initView() {
with(binding) {
- luckyNumberSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
+ luckyNumberSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ luckyNumberSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ luckyNumberSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
luckyNumberErrorRetry.setOnClickListener { presenter.onRetry() }
luckyNumberErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
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 25b41aab..5d93c594 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
@@ -14,6 +14,7 @@ import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
+import android.view.View
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.view.ViewCompat
@@ -28,7 +29,7 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.ActivityMainBinding
import io.github.wulkanowy.ui.base.BaseActivity
-import io.github.wulkanowy.ui.modules.account.AccountDialog
+import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
@@ -65,17 +66,19 @@ class MainActivity : BaseActivity(), MainVie
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
- private val navController = FragNavController(supportFragmentManager, R.id.mainFragmentContainer)
+ private val navController =
+ FragNavController(supportFragmentManager, R.id.mainFragmentContainer)
companion object {
const val EXTRA_START_MENU = "extraStartMenu"
- fun getStartIntent(context: Context, startMenu: MainView.Section? = null, clear: Boolean = false): Intent {
- return Intent(context, MainActivity::class.java)
- .apply {
- if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
- startMenu?.let { putExtra(EXTRA_START_MENU, it.id) }
- }
+ fun getStartIntent(
+ context: Context,
+ startMenu: MainView.Section? = null,
+ clear: Boolean = false
+ ) = Intent(context, MainActivity::class.java).apply {
+ if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
+ startMenu?.let { putExtra(EXTRA_START_MENU, it.id) }
}
}
@@ -83,7 +86,10 @@ class MainActivity : BaseActivity(), MainVie
override val currentStackSize get() = navController.currentStack?.size
- override val currentViewTitle get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let { getString(it) }
+ override val currentViewTitle
+ get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let {
+ getString(it)
+ }
override val currentViewSubtitle get() = (navController.currentFrag as? MainView.TitledView)?.subtitleString
@@ -106,7 +112,10 @@ class MainActivity : BaseActivity(), MainVie
messageContainer = binding.mainFragmentContainer
updateHelper.messageContainer = binding.mainFragmentContainer
- presenter.onAttachView(this, MainView.Section.values().singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) })
+ val section = MainView.Section.values()
+ .singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) }
+
+ presenter.onAttachView(this, section)
with(navController) {
initialize(startMenuIndex, savedInstanceState)
@@ -132,21 +141,49 @@ class MainActivity : BaseActivity(), MainVie
val shortcutsList = mutableListOf()
listOf(
- Triple(getString(R.string.grade_title), R.drawable.ic_shortcut_grade, MainView.Section.GRADE),
- Triple(getString(R.string.attendance_title), R.drawable.ic_shortcut_attendance, MainView.Section.ATTENDANCE),
- Triple(getString(R.string.exam_title), R.drawable.ic_shortcut_exam, MainView.Section.EXAM),
- Triple(getString(R.string.timetable_title), R.drawable.ic_shortcut_timetable, MainView.Section.TIMETABLE),
- Triple(getString(R.string.message_title), R.drawable.ic_shortcut_message, MainView.Section.MESSAGE)
+ Triple(
+ getString(R.string.grade_title),
+ R.drawable.ic_shortcut_grade,
+ MainView.Section.GRADE
+ ),
+ Triple(
+ getString(R.string.attendance_title),
+ R.drawable.ic_shortcut_attendance,
+ MainView.Section.ATTENDANCE
+ ),
+ Triple(
+ getString(R.string.exam_title),
+ R.drawable.ic_shortcut_exam,
+ MainView.Section.EXAM
+ ),
+ Triple(
+ getString(R.string.timetable_title),
+ R.drawable.ic_shortcut_timetable,
+ MainView.Section.TIMETABLE
+ ),
+ Triple(
+ getString(R.string.message_title),
+ R.drawable.ic_shortcut_message,
+ MainView.Section.MESSAGE
+ )
).forEach { (title, icon, enum) ->
- shortcutsList.add(ShortcutInfo.Builder(applicationContext, title)
- .setShortLabel(title)
- .setLongLabel(title)
- .setIcon(Icon.createWithResource(applicationContext, icon))
- .setIntents(arrayOf(
- Intent(applicationContext, MainActivity::class.java).setAction(Intent.ACTION_VIEW),
- Intent(applicationContext, MainActivity::class.java).putExtra(EXTRA_START_MENU, enum.id)
- .setAction(Intent.ACTION_VIEW).addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)))
- .build())
+ shortcutsList.add(
+ ShortcutInfo.Builder(applicationContext, title)
+ .setShortLabel(title)
+ .setLongLabel(title)
+ .setIcon(Icon.createWithResource(applicationContext, icon))
+ .setIntents(
+ arrayOf(
+ Intent(applicationContext, MainActivity::class.java)
+ .setAction(Intent.ACTION_VIEW),
+ Intent(applicationContext, MainActivity::class.java)
+ .putExtra(EXTRA_START_MENU, enum.id)
+ .setAction(Intent.ACTION_VIEW)
+ .addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ )
+ .build()
+ )
}
getSystemService()?.dynamicShortcuts = shortcutsList
@@ -160,20 +197,33 @@ class MainActivity : BaseActivity(), MainVie
override fun initView() {
with(binding.mainToolbar) {
if (SDK_INT >= LOLLIPOP) stateListAnimator = null
- setBackgroundColor(overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f)))
+ setBackgroundColor(
+ overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f))
+ )
}
with(binding.mainBottomNav) {
- addItems(listOf(
- AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_main_grade, 0),
- AHBottomNavigationItem(R.string.attendance_title, R.drawable.ic_main_attendance, 0),
- AHBottomNavigationItem(R.string.exam_title, R.drawable.ic_main_exam, 0),
- AHBottomNavigationItem(R.string.timetable_title, R.drawable.ic_main_timetable, 0),
- AHBottomNavigationItem(R.string.more_title, R.drawable.ic_main_more, 0)
- ))
+ addItems(
+ listOf(
+ AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_main_grade, 0),
+ AHBottomNavigationItem(
+ R.string.attendance_title,
+ R.drawable.ic_main_attendance,
+ 0
+ ),
+ AHBottomNavigationItem(R.string.exam_title, R.drawable.ic_main_exam, 0),
+ AHBottomNavigationItem(
+ R.string.timetable_title,
+ R.drawable.ic_main_timetable,
+ 0
+ ),
+ AHBottomNavigationItem(R.string.more_title, R.drawable.ic_main_more, 0)
+ )
+ )
accentColor = getThemeAttrColor(R.attr.colorPrimary)
inactiveColor = getThemeAttrColor(R.attr.colorOnSurface, 153)
- defaultBackgroundColor = overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(8f))
+ defaultBackgroundColor =
+ overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(8f))
titleState = ALWAYS_SHOW
currentItem = startMenuIndex
isBehaviorTranslationEnabled = false
@@ -183,6 +233,13 @@ class MainActivity : BaseActivity(), MainVie
with(navController) {
setOnViewChangeListener { section, name ->
+ binding.mainBottomNav.visibility =
+ if (section == MainView.Section.ACCOUNT || section == MainView.Section.STUDENT_INFO) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
+
analytics.setCurrentScreen(this@MainActivity, name)
presenter.onViewChange(section)
}
@@ -224,7 +281,7 @@ class MainActivity : BaseActivity(), MainVie
}
override fun showAccountPicker() {
- navController.showDialogFragment(AccountDialog.newInstance())
+ navController.showDialogFragment(AccountQuickDialog.newInstance())
}
override fun showActionBarElevation(show: Boolean) {
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 97b556e3..7f409814 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
@@ -64,6 +64,8 @@ interface MainView : BaseView {
LUCKY_NUMBER(8),
SETTINGS(9),
ABOUT(10),
- SCHOOL(11)
+ SCHOOL(11),
+ ACCOUNT(12),
+ STUDENT_INFO(13)
}
}
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 55b9631a..702e5467 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
@@ -70,6 +70,7 @@ class MessagePreviewPresenter @Inject constructor(
this@MessagePreviewPresenter.attachments = it.data.attachments
view?.apply {
setMessageWithAttachment(it.data)
+ showContent(true)
initOptions()
}
analytics.logEvent(
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 48a2cee2..f08059f2 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
@@ -19,6 +19,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
+import io.github.wulkanowy.utils.getThemeAttrColor
import kotlinx.coroutines.FlowPreview
import javax.inject.Inject
@@ -74,7 +75,9 @@ class MessageTabFragment : BaseFragment(R.layout.frag
addItemDecoration(DividerItemDecoration(context))
}
with(binding) {
- messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
+ messageTabSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ messageTabSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ messageTabSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt
index 48737d7b..f8e367c5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt
@@ -16,6 +16,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
+import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
@@ -56,7 +57,9 @@ class MobileDeviceFragment :
}
with(binding) {
- mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
+ mobileDevicesSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ mobileDevicesSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ mobileDevicesSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() }
mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() }
mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt
index 40378265..dd622344 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt
@@ -13,6 +13,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
+import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
@@ -50,7 +51,9 @@ class NoteFragment : BaseFragment(R.layout.fragment_note),
addItemDecoration(DividerItemDecoration(context))
}
with(binding) {
- noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
+ noteSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ noteSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ noteSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
noteErrorRetry.setOnClickListener { presenter.onRetry() }
noteErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt
index d3f11007..03ad7ba0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolFragment.kt
@@ -12,6 +12,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
+import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.openDialer
import io.github.wulkanowy.utils.openNavigation
import javax.inject.Inject
@@ -39,7 +40,9 @@ class SchoolFragment : BaseFragment(R.layout.fragment_sch
override fun initView() {
with(binding) {
- schoolSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
+ schoolSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ schoolSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ schoolSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
schoolErrorRetry.setOnClickListener { presenter.onRetry() }
schoolErrorDetails.setOnClickListener { presenter.onDetailsClick() }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt
index 491a19ec..202d4e5d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt
@@ -81,10 +81,7 @@ class SchoolPresenter @Inject constructor(
showEmpty(false)
showErrorView(false)
}
- analytics.logEvent(
- "load_item",
- "type" to "school"
- )
+ analytics.logEvent("load_item", "type" to "school")
} else view?.run {
Timber.i("Loading school result: No school info found")
showContent(!isViewEmpty)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt
index 5914945b..b052a383 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherFragment.kt
@@ -14,6 +14,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
+import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
@@ -51,7 +52,9 @@ class TeacherFragment : BaseFragment(R.layout.fragment_t
addItemDecoration(DividerItemDecoration(context))
}
with(binding) {
- teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
+ teacherSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ teacherSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ teacherSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
teacherErrorRetry.setOnClickListener { presenter.onRetry() }
teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
index bda07e34..ad4692b9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
@@ -79,6 +79,10 @@ class SettingsFragment : PreferenceFragmentCompat(),
lingver.setLocale(requireContext(), langCode)
}
+ override fun updateLanguageToFollowSystem() {
+ lingver.setFollowSystemLocale(requireContext())
+ }
+
override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) {
findPreference(serviceEnablesKey)?.run {
summary = if (isHolidays) getString(R.string.pref_services_suspended) else ""
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt
index f9ad74a1..e3b2e232 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt
@@ -42,14 +42,18 @@ class SettingsPresenter @Inject constructor(
when (key) {
serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() }
servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true)
- isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable
+ isDebugNotificationEnableKey -> chuckerCollector.showNotification =
+ isDebugNotificationEnable
appThemeKey -> view?.recreateView()
isUpcomingLessonsNotificationsEnableKey -> if (!isUpcomingLessonsNotificationsEnable) timetableNotificationHelper.cancelNotification()
appLanguageKey -> view?.run {
- val newLang = if (appLanguage == "system") appInfo.systemLanguage else appLanguage
- analytics.logEvent("language", "setting_changed" to newLang)
-
- updateLanguage(newLang)
+ if (appLanguage == "system") {
+ updateLanguageToFollowSystem()
+ analytics.logEvent("language", "setting_changed" to appInfo.systemLanguage)
+ } else {
+ updateLanguage(appLanguage)
+ analytics.logEvent("language", "setting_changed" to appLanguage)
+ }
recreateView()
}
}
@@ -71,7 +75,10 @@ class SettingsPresenter @Inject constructor(
analytics.logEvent("sync_now", "status" to "success")
}
WorkInfo.State.FAILED -> {
- showError(syncFailedString, Throwable(workInfo.outputData.getString("error")))
+ showError(
+ syncFailedString,
+ Throwable(workInfo.outputData.getString("error"))
+ )
analytics.logEvent("sync_now", "status" to "failed")
}
else -> Timber.d("Sync now state: ${workInfo.state}")
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt
index b647c0b7..80271741 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt
@@ -14,6 +14,8 @@ interface SettingsView : BaseView {
fun updateLanguage(langCode: String)
+ fun updateLanguageToFollowSystem()
+
fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean)
fun setSyncInProgress(inProgress: Boolean)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoAdapter.kt
new file mode 100644
index 00000000..602ec07a
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoAdapter.kt
@@ -0,0 +1,42 @@
+package io.github.wulkanowy.ui.modules.studentinfo
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import io.github.wulkanowy.databinding.ItemStudentInfoBinding
+import javax.inject.Inject
+
+class StudentInfoAdapter @Inject constructor() :
+ RecyclerView.Adapter() {
+
+ var items = listOf>()
+
+ var onItemClickListener: (position: Int) -> Unit = {}
+
+ var onItemLongClickListener: (text: String) -> Unit = {}
+
+ override fun getItemCount() = items.size
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
+ ItemStudentInfoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ )
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val item = items[position]
+
+ with(holder.binding) {
+ studentInfoItemTitle.text = item.first
+ studentInfoItemSubtitle.text = item.second
+
+ with(root) {
+ setOnClickListener { onItemClickListener(position) }
+ setOnLongClickListener {
+ onItemLongClickListener(studentInfoItemSubtitle.text.toString())
+ true
+ }
+ }
+ }
+ }
+
+ class ViewHolder(val binding: ItemStudentInfoBinding) : RecyclerView.ViewHolder(binding.root)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt
new file mode 100644
index 00000000..767d31a4
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt
@@ -0,0 +1,232 @@
+package io.github.wulkanowy.ui.modules.studentinfo
+
+import android.annotation.SuppressLint
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.View
+import android.widget.Toast
+import androidx.core.content.getSystemService
+import androidx.core.view.get
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.StudentInfo
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.enums.Gender
+import io.github.wulkanowy.databinding.FragmentStudentInfoBinding
+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.utils.getThemeAttrColor
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class StudentInfoFragment :
+ BaseFragment(R.layout.fragment_student_info), StudentInfoView,
+ MainView.TitledView {
+
+ @Inject
+ lateinit var presenter: StudentInfoPresenter
+
+ @Inject
+ lateinit var studentInfoAdapter: StudentInfoAdapter
+
+ override val titleStringId: Int
+ get() = R.string.student_info_title
+
+ override val isViewEmpty get() = studentInfoAdapter.items.isEmpty()
+
+ companion object {
+
+ private const val INFO_TYPE_ARGUMENT_KEY = "info_type"
+
+ private const val STUDENT_ARGUMENT_KEY = "student_with_semesters"
+
+ fun newInstance(type: StudentInfoView.Type, studentWithSemesters: StudentWithSemesters) =
+ StudentInfoFragment().apply {
+ arguments = Bundle().apply {
+ putSerializable(INFO_TYPE_ARGUMENT_KEY, type)
+ putSerializable(STUDENT_ARGUMENT_KEY, studentWithSemesters)
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding = FragmentStudentInfoBinding.bind(view)
+ presenter.onAttachView(
+ this,
+ requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as StudentInfoView.Type,
+ requireArguments().getSerializable(STUDENT_ARGUMENT_KEY) as StudentWithSemesters
+ )
+ }
+
+ override fun initView() {
+ with(binding) {
+ studentInfoSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ studentInfoSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ studentInfoSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
+ studentInfoErrorRetry.setOnClickListener { presenter.onRetry() }
+ studentInfoErrorDetails.setOnClickListener { presenter.onDetailsClick() }
+ }
+
+ with(studentInfoAdapter) {
+ onItemClickListener = presenter::onItemSelected
+ onItemLongClickListener = presenter::onItemLongClick
+ }
+
+ with(binding.studentInfoRecycler) {
+ layoutManager = LinearLayoutManager(context)
+ addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL))
+ setHasFixedSize(true)
+ adapter = studentInfoAdapter
+ }
+ }
+
+ override fun updateData(data: List>) {
+ with(studentInfoAdapter) {
+ items = data
+ notifyDataSetChanged()
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ menu[0].isVisible = false
+ }
+
+ override fun showPersonalTypeData(studentInfo: StudentInfo) {
+ updateData(
+ listOf(
+ getString(R.string.student_info_first_name) to studentInfo.firstName,
+ getString(R.string.student_info_second_name) to studentInfo.secondName,
+ getString(R.string.student_info_last_name) to studentInfo.surname,
+ getString(R.string.student_info_gender) to getString(if (studentInfo.gender == Gender.MALE) R.string.student_info_male else R.string.student_info_female),
+ getString(R.string.student_info_polish_citizenship) to getString(if (studentInfo.hasPolishCitizenship) R.string.all_yes else R.string.all_no),
+ getString(R.string.student_info_family_name) to studentInfo.familyName,
+ getString(R.string.student_info_parents_name) to studentInfo.parentsNames
+ ).map {
+ if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
+ }
+ )
+ }
+
+ override fun showContactTypeData(studentInfo: StudentInfo) {
+ updateData(
+ listOf(
+ getString(R.string.student_info_phone) to studentInfo.phoneNumber,
+ getString(R.string.student_info_cellphone) to studentInfo.cellPhoneNumber,
+ getString(R.string.student_info_email) to studentInfo.email
+ ).map {
+ if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
+ }
+ )
+ }
+
+ @SuppressLint("DefaultLocale")
+ override fun showFamilyTypeData(studentInfo: StudentInfo) {
+ updateData(
+ listOf(
+ studentInfo.firstGuardian.kinship.capitalize() to studentInfo.firstGuardian.fullName,
+ studentInfo.secondGuardian.kinship.capitalize() to studentInfo.secondGuardian.fullName
+ ).map {
+ if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
+ }
+ )
+ }
+
+ override fun showAddressTypeData(studentInfo: StudentInfo) {
+ updateData(
+ listOf(
+ getString(R.string.student_info_address) to studentInfo.address,
+ getString(R.string.student_info_registered_address) to studentInfo.registeredAddress,
+ getString(R.string.student_info_correspondence_address) to studentInfo.correspondenceAddress
+ ).map {
+ if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
+ }
+ )
+ }
+
+ override fun showFirstGuardianTypeData(studentInfo: StudentInfo) {
+ updateData(
+ listOf(
+ getString(R.string.student_info_full_name) to studentInfo.firstGuardian.fullName,
+ getString(R.string.student_info_kinship) to studentInfo.firstGuardian.kinship,
+ getString(R.string.student_info_guardian_address) to studentInfo.firstGuardian.address,
+ getString(R.string.student_info_phones) to studentInfo.firstGuardian.phones,
+ getString(R.string.student_info_email) to studentInfo.firstGuardian.email
+ ).map {
+ if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
+ }
+ )
+ }
+
+ override fun showSecondGuardianTypeData(studentInfo: StudentInfo) {
+ updateData(
+ listOf(
+ getString(R.string.student_info_full_name) to studentInfo.secondGuardian.fullName,
+ getString(R.string.student_info_kinship) to studentInfo.secondGuardian.kinship,
+ getString(R.string.student_info_guardian_address) to studentInfo.secondGuardian.address,
+ getString(R.string.student_info_phones) to studentInfo.secondGuardian.phones,
+ getString(R.string.student_info_email) to studentInfo.secondGuardian.email
+ ).map {
+ if (it.second.isBlank()) it.copy(second = getString(R.string.all_no_data)) else it
+ }
+ )
+ }
+
+ override fun openStudentInfoView(
+ infoType: StudentInfoView.Type,
+ studentWithSemesters: StudentWithSemesters
+ ) {
+ (requireActivity() as MainActivity).pushView(newInstance(infoType, studentWithSemesters))
+ }
+
+ override fun showEmpty(show: Boolean) {
+ binding.studentInfoEmpty.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
+ override fun showErrorView(show: Boolean) {
+ binding.studentInfoError.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
+ override fun setErrorDetails(message: String) {
+ binding.studentInfoErrorMessage.text = message
+ }
+
+ override fun showProgress(show: Boolean) {
+ binding.studentInfoProgress.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
+ override fun enableSwipe(enable: Boolean) {
+ binding.studentInfoSwipe.isEnabled = enable
+ }
+
+ override fun showContent(show: Boolean) {
+ binding.studentInfoRecycler.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
+ override fun hideRefresh() {
+ binding.studentInfoSwipe.isRefreshing = false
+ }
+
+ override fun copyToClipboard(text: String) {
+ val clipData = ClipData.newPlainText("student_info_wulkanowy", text)
+ requireActivity().getSystemService()?.setPrimaryClip(clipData)
+ Toast.makeText(context, R.string.all_copied, Toast.LENGTH_SHORT).show()
+ }
+
+ override fun onDestroyView() {
+ presenter.onDetachView()
+ super.onDestroyView()
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt
new file mode 100644
index 00000000..a1a48be1
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt
@@ -0,0 +1,142 @@
+package io.github.wulkanowy.ui.modules.studentinfo
+
+import io.github.wulkanowy.data.Status
+import io.github.wulkanowy.data.db.entities.StudentInfo
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.data.repositories.StudentInfoRepository
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.ui.base.ErrorHandler
+import io.github.wulkanowy.utils.AnalyticsHelper
+import io.github.wulkanowy.utils.afterLoading
+import io.github.wulkanowy.utils.flowWithResourceIn
+import io.github.wulkanowy.utils.getCurrentOrLast
+import kotlinx.coroutines.flow.onEach
+import timber.log.Timber
+import javax.inject.Inject
+
+class StudentInfoPresenter @Inject constructor(
+ errorHandler: ErrorHandler,
+ studentRepository: StudentRepository,
+ private val studentInfoRepository: StudentInfoRepository,
+ private val analytics: AnalyticsHelper
+) : BasePresenter(errorHandler, studentRepository) {
+
+ private lateinit var infoType: StudentInfoView.Type
+
+ private lateinit var studentWithSemesters: StudentWithSemesters
+
+ private lateinit var lastError: Throwable
+
+ fun onAttachView(
+ view: StudentInfoView,
+ type: StudentInfoView.Type,
+ studentWithSemesters: StudentWithSemesters
+ ) {
+ super.onAttachView(view)
+ infoType = type
+ this.studentWithSemesters = studentWithSemesters
+ view.initView()
+ Timber.i("Student info $infoType view was initialized")
+ errorHandler.showErrorMessage = ::showErrorViewOnError
+ loadData()
+ }
+
+ fun onSwipeRefresh() {
+ loadData(true)
+ }
+
+ fun onRetry() {
+ view?.run {
+ showErrorView(false)
+ showProgress(true)
+ }
+ loadData(true)
+ }
+
+ fun onDetailsClick() {
+ view?.showErrorDetailsDialog(lastError)
+ }
+
+ fun onItemSelected(position: Int) {
+ if (infoType != StudentInfoView.Type.FAMILY) return
+
+ if (position == 0) {
+ view?.openStudentInfoView(StudentInfoView.Type.FIRST_GUARDIAN, studentWithSemesters)
+ } else {
+ view?.openStudentInfoView(StudentInfoView.Type.SECOND_GUARDIAN, studentWithSemesters)
+ }
+ }
+
+ fun onItemLongClick(text: String) {
+ view?.copyToClipboard(text)
+ }
+
+ private fun loadData(forceRefresh: Boolean = false) {
+ flowWithResourceIn {
+ val semester = studentWithSemesters.semesters.getCurrentOrLast()
+ studentInfoRepository.getStudentInfo(
+ studentWithSemesters.student,
+ semester,
+ forceRefresh
+ )
+ }.onEach {
+ when (it.status) {
+ Status.LOADING -> Timber.i("Loading student info $infoType started")
+ Status.SUCCESS -> {
+ if (it.data != null) {
+ Timber.i("Loading student info $infoType result: Success")
+ showCorrectData(it.data)
+ view?.run {
+ showContent(true)
+ showEmpty(false)
+ showErrorView(false)
+ }
+ analytics.logEvent("load_item", "type" to "student_info")
+ } else {
+ Timber.i("Loading student info $infoType result: No school info found")
+ view?.run {
+ showContent(!isViewEmpty)
+ showEmpty(isViewEmpty)
+ showErrorView(false)
+ }
+ }
+ }
+ Status.ERROR -> {
+ Timber.i("Loading student info $infoType result: An exception occurred")
+ errorHandler.dispatch(it.error!!)
+ }
+ }
+ }.afterLoading {
+ view?.run {
+ hideRefresh()
+ showProgress(false)
+ enableSwipe(true)
+ }
+ }.launch()
+ }
+
+ private fun showCorrectData(studentInfo: StudentInfo) {
+ when (infoType) {
+ StudentInfoView.Type.PERSONAL -> view?.showPersonalTypeData(studentInfo)
+ StudentInfoView.Type.CONTACT -> view?.showContactTypeData(studentInfo)
+ StudentInfoView.Type.ADDRESS -> view?.showAddressTypeData(studentInfo)
+ StudentInfoView.Type.FAMILY -> view?.showFamilyTypeData(studentInfo)
+ StudentInfoView.Type.SECOND_GUARDIAN -> view?.showSecondGuardianTypeData(studentInfo)
+ StudentInfoView.Type.FIRST_GUARDIAN -> view?.showFirstGuardianTypeData(studentInfo)
+ }
+ }
+
+ private fun showErrorViewOnError(message: String, error: Throwable) {
+ view?.run {
+ if (isViewEmpty) {
+ lastError = error
+ setErrorDetails(message)
+ showErrorView(true)
+ showEmpty(false)
+ showContent(false)
+ showProgress(false)
+ } else showError(message, error)
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoView.kt
new file mode 100644
index 00000000..70c3eb5e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoView.kt
@@ -0,0 +1,48 @@
+package io.github.wulkanowy.ui.modules.studentinfo
+
+import io.github.wulkanowy.data.db.entities.StudentInfo
+import io.github.wulkanowy.data.db.entities.StudentWithSemesters
+import io.github.wulkanowy.ui.base.BaseView
+
+interface StudentInfoView : BaseView {
+
+ enum class Type {
+ PERSONAL, ADDRESS, CONTACT, FAMILY, FIRST_GUARDIAN, SECOND_GUARDIAN
+ }
+
+ val isViewEmpty: Boolean
+
+ fun initView()
+
+ fun updateData(data: List>)
+
+ fun showPersonalTypeData(studentInfo: StudentInfo)
+
+ fun showContactTypeData(studentInfo: StudentInfo)
+
+ fun showAddressTypeData(studentInfo: StudentInfo)
+
+ fun showFamilyTypeData(studentInfo: StudentInfo)
+
+ fun showFirstGuardianTypeData(studentInfo: StudentInfo)
+
+ fun showSecondGuardianTypeData(studentInfo: StudentInfo)
+
+ fun openStudentInfoView(infoType: Type, studentWithSemesters: StudentWithSemesters)
+
+ fun showEmpty(show: Boolean)
+
+ fun showErrorView(show: Boolean)
+
+ fun setErrorDetails(message: String)
+
+ fun showProgress(show: Boolean)
+
+ fun enableSwipe(enable: Boolean)
+
+ fun showContent(show: Boolean)
+
+ fun hideRefresh()
+
+ fun copyToClipboard(text: String)
+}
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 1bb9c920..91f09ccc 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,6 +21,7 @@ import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragme
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.getThemeAttrColor
import java.time.LocalDate
import javax.inject.Inject
@@ -69,6 +70,8 @@ class TimetableFragment : BaseFragment(R.layout.fragme
with(binding) {
timetableSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ timetableSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ timetableSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
timetableErrorRetry.setOnClickListener { presenter.onRetry() }
timetableErrorDetails.setOnClickListener { presenter.onDetailsClick() }
@@ -176,6 +179,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme
setDateRangeLimiter(SchooldaysRangeLimiter())
version = DatePickerDialog.Version.VERSION_2
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
+ vibrate(false)
show(this@TimetableFragment.parentFragmentManager, null)
}
}
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 17a1cabe..18551faa 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,6 +13,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
import io.github.wulkanowy.utils.dpToPx
+import io.github.wulkanowy.utils.getThemeAttrColor
import java.time.LocalDate
import javax.inject.Inject
@@ -53,6 +54,8 @@ class AdditionalLessonsFragment :
with(binding) {
additionalLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ additionalLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ additionalLessonsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
additionalLessonsErrorRetry.setOnClickListener { presenter.onRetry() }
additionalLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() }
@@ -128,6 +131,7 @@ class AdditionalLessonsFragment :
setDateRangeLimiter(SchooldaysRangeLimiter())
version = DatePickerDialog.Version.VERSION_2
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
+ vibrate(false)
show(this@AdditionalLessonsFragment.parentFragmentManager, null)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt
index 828e1001..b6041b8a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt
@@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getCompatDrawable
+import io.github.wulkanowy.utils.getThemeAttrColor
import java.time.LocalDate
import javax.inject.Inject
@@ -60,6 +61,8 @@ class CompletedLessonsFragment :
with(binding) {
completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
+ completedLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
+ completedLessonsSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
completedLessonErrorRetry.setOnClickListener { presenter.onRetry() }
completedLessonErrorDetails.setOnClickListener { presenter.onDetailsClick() }
@@ -147,6 +150,7 @@ class CompletedLessonsFragment :
setDateRangeLimiter(SchooldaysRangeLimiter())
version = DatePickerDialog.Version.VERSION_2
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
+ vibrate(false)
show(this@CompletedLessonsFragment.parentFragmentManager, null)
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt
index 938be98d..1d63f094 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt
@@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
+import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.previousSchoolDay
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.GlobalScope
@@ -151,8 +152,14 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
val remoteView = RemoteViews(context.packageName, layoutId).apply {
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
- setTextViewText(R.id.timetableWidgetDate, date.toFormattedString("EEEE, dd.MM").capitalize())
- setTextViewText(R.id.timetableWidgetName, student?.studentName ?: context.getString(R.string.all_no_data))
+ setTextViewText(
+ R.id.timetableWidgetDate,
+ date.toFormattedString("EEEE, dd.MM").capitalize()
+ )
+ setTextViewText(
+ R.id.timetableWidgetName,
+ student?.nickOrName ?: context.getString(R.string.all_no_data)
+ )
setRemoteAdapter(R.id.timetableWidgetList, adapterIntent)
setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent)
setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent)
diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt b/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt
index 1098a9a0..82671a7f 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt
@@ -4,7 +4,9 @@ import android.content.res.Resources
import android.os.Build.MANUFACTURER
import android.os.Build.MODEL
import android.os.Build.VERSION.SDK_INT
+import io.github.wulkanowy.BuildConfig.BUILD_TIMESTAMP
import io.github.wulkanowy.BuildConfig.DEBUG
+import io.github.wulkanowy.BuildConfig.FLAVOR
import io.github.wulkanowy.BuildConfig.VERSION_CODE
import io.github.wulkanowy.BuildConfig.VERSION_NAME
import javax.inject.Inject
@@ -17,6 +19,10 @@ open class AppInfo @Inject constructor() {
open val versionCode get() = VERSION_CODE
+ open val buildTimestamp get() = BUILD_TIMESTAMP
+
+ open val buildFlavor get() = FLAVOR
+
open val versionName get() = VERSION_NAME
open val systemVersion get() = SDK_INT
@@ -28,4 +34,9 @@ open class AppInfo @Inject constructor() {
@Suppress("DEPRECATION")
open val systemLanguage: String
get() = Resources.getSystem().configuration.locale.language
+
+ open val defaultColorsForAvatar = listOf(
+ 0xe57373, 0xf06292, 0xba68c8, 0x9575cd, 0x7986cb, 0x64b5f6, 0x4fc3f7, 0x4dd0e1, 0x4db6ac,
+ 0x81c784, 0xaed581, 0xff8a65, 0xd4e157, 0xffd54f, 0xffb74d, 0xa1887f, 0x90a4ae
+ )
}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt
index 2d074b22..049e1d42 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt
@@ -2,6 +2,7 @@ package io.github.wulkanowy.utils
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.Status
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
@@ -71,24 +72,14 @@ inline fun networkBoundResource(
fun flowWithResource(block: suspend () -> T) = flow {
emit(Resource.loading())
- emit(try {
- Resource.success(block())
- } catch (e: Throwable) {
- Resource.error(e)
- })
-}
+ emit(Resource.success(block()))
+}.catch { emit(Resource.error(it)) }
+@OptIn(FlowPreview::class)
fun flowWithResourceIn(block: suspend () -> Flow>) = flow {
emit(Resource.loading())
-
- block()
- .catch { emit(Resource.error(it)) }
- .collect {
- if (it.status != Status.LOADING || (it.status == Status.LOADING && it.data != null)) { // LOADING without data is already emitted
- emit(it)
- }
- }
-}
+ emitAll(block().filter { it.status != Status.LOADING || (it.status == Status.LOADING && it.data != null) })
+}.catch { emit(Resource.error(it)) }
fun Flow>.afterLoading(callback: () -> Unit) = onEach {
if (it.status != Status.LOADING) callback()
@@ -96,4 +87,5 @@ fun Flow>.afterLoading(callback: () -> Unit) = onEach {
suspend fun Flow>.toFirstResult() = filter { it.status != Status.LOADING }.first()
-suspend fun Flow>.waitForResult() = takeWhile { it.status == Status.LOADING }.collect()
+suspend fun Flow>.waitForResult() =
+ takeWhile { it.status == Status.LOADING }.collect()
diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt
index 0b71c964..a49360d7 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt
@@ -2,6 +2,8 @@ package io.github.wulkanowy.utils
import androidx.fragment.app.Fragment
import io.github.wulkanowy.ui.modules.about.AboutFragment
+import io.github.wulkanowy.ui.modules.account.AccountFragment
+import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
@@ -13,6 +15,7 @@ import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
+import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
fun Fragment.toSection(): MainView.Section? {
@@ -29,6 +32,9 @@ fun Fragment.toSection(): MainView.Section? {
is SettingsFragment -> MainView.Section.SETTINGS
is AboutFragment -> MainView.Section.ABOUT
is SchoolAndTeachersFragment -> MainView.Section.SCHOOL
+ is AccountFragment -> MainView.Section.ACCOUNT
+ is AccountDetailsFragment -> MainView.Section.ACCOUNT
+ is StudentInfoFragment -> MainView.Section.STUDENT_INFO
else -> null
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt
index c57b6247..9aa4fcac 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt
@@ -16,63 +16,58 @@ fun List.calcAverage(): Double {
}
@JvmName("calcSummaryAverage")
-fun List.calcAverage(): Double {
- return asSequence().mapNotNull {
- if (it.finalGrade.matches("[0-6]".toRegex())) it.finalGrade.toDouble() else null
- }.average().let { if (it.isNaN()) 0.0 else it }
-}
-
-fun Grade.getBackgroundColor(theme: String): Int {
- return when (theme) {
- "grade_color" -> getGradeColor()
- "material" -> when (value.toInt()) {
- 6 -> R.color.grade_material_six
- 5 -> R.color.grade_material_five
- 4 -> R.color.grade_material_four
- 3 -> R.color.grade_material_three
- 2 -> R.color.grade_material_two
- 1 -> R.color.grade_material_one
- else -> R.color.grade_material_default
- }
- else -> when (value.toInt()) {
- 6 -> R.color.grade_vulcan_six
- 5 -> R.color.grade_vulcan_five
- 4 -> R.color.grade_vulcan_four
- 3 -> R.color.grade_vulcan_three
- 2 -> R.color.grade_vulcan_two
- 1 -> R.color.grade_vulcan_one
- else -> R.color.grade_vulcan_default
- }
+fun List.calcAverage() = asSequence()
+ .mapNotNull {
+ if (it.finalGrade.matches("[0-6]".toRegex())) {
+ it.finalGrade.toDouble()
+ } else null
}
-}
+ .average()
+ .let { if (it.isNaN()) 0.0 else it }
-fun Grade.getGradeColor(): Int {
- return when (color) {
- "000000" -> R.color.grade_black
- "F04C4C" -> R.color.grade_red
- "20A4F7" -> R.color.grade_blue
- "6ECD07" -> R.color.grade_green
- "B16CF1" -> R.color.grade_purple
+fun Grade.getBackgroundColor(theme: String) = when (theme) {
+ "grade_color" -> getGradeColor()
+ "material" -> when (value.toInt()) {
+ 6 -> R.color.grade_material_six
+ 5 -> R.color.grade_material_five
+ 4 -> R.color.grade_material_four
+ 3 -> R.color.grade_material_three
+ 2 -> R.color.grade_material_two
+ 1 -> R.color.grade_material_one
else -> R.color.grade_material_default
}
+ else -> when (value.toInt()) {
+ 6 -> R.color.grade_vulcan_six
+ 5 -> R.color.grade_vulcan_five
+ 4 -> R.color.grade_vulcan_four
+ 3 -> R.color.grade_vulcan_three
+ 2 -> R.color.grade_vulcan_two
+ 1 -> R.color.grade_vulcan_one
+ else -> R.color.grade_vulcan_default
+ }
+}
+
+fun Grade.getGradeColor() = when (color) {
+ "000000" -> R.color.grade_black
+ "F04C4C" -> R.color.grade_red
+ "20A4F7" -> R.color.grade_blue
+ "6ECD07" -> R.color.grade_green
+ "B16CF1" -> R.color.grade_purple
+ else -> R.color.grade_material_default
}
inline val Grade.colorStringId: Int
- get() {
- return when (color) {
- "000000" -> R.string.all_black
- "F04C4C" -> R.string.all_red
- "20A4F7" -> R.string.all_blue
- "6ECD07" -> R.string.all_green
- "B16CF1" -> R.string.all_purple
- else -> R.string.all_empty_color
- }
+ get() = when (color) {
+ "000000" -> R.string.all_black
+ "F04C4C" -> R.string.all_red
+ "20A4F7" -> R.string.all_blue
+ "6ECD07" -> R.string.all_green
+ "B16CF1" -> R.string.all_purple
+ else -> R.string.all_empty_color
}
-fun Grade.changeModifier(plusModifier: Double, minusModifier: Double): Grade {
- return when {
- modifier > 0 -> copy(modifier = plusModifier)
- modifier < 0 -> copy(modifier = -minusModifier)
- else -> this
- }
+fun Grade.changeModifier(plusModifier: Double, minusModifier: Double) = when {
+ modifier > 0 -> copy(modifier = plusModifier)
+ modifier < 0 -> copy(modifier = -minusModifier)
+ else -> this
}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt
new file mode 100644
index 00000000..fdd0610a
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt
@@ -0,0 +1,5 @@
+package io.github.wulkanowy.utils
+
+import io.github.wulkanowy.data.db.entities.Student
+
+inline val Student.nickOrName get() = if (nick.isBlank()) studentName else nick
diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt
index 9bd30e87..d80abbd1 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt
@@ -1,6 +1,7 @@
package io.github.wulkanowy.utils
import android.annotation.SuppressLint
+import java.text.SimpleDateFormat
import java.time.DayOfWeek.FRIDAY
import java.time.DayOfWeek.MONDAY
import java.time.DayOfWeek.SATURDAY
@@ -8,12 +9,12 @@ import java.time.DayOfWeek.SUNDAY
import java.time.Instant.ofEpochMilli
import java.time.LocalDate
import java.time.LocalDateTime
+import java.time.LocalDateTime.now
import java.time.LocalDateTime.ofInstant
import java.time.Month
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter.ofPattern
-import java.time.format.TextStyle.FULL
import java.time.temporal.TemporalAdjusters.firstInMonth
import java.time.temporal.TemporalAdjusters.next
import java.time.temporal.TemporalAdjusters.previous
@@ -33,24 +34,10 @@ fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = for
@SuppressLint("DefaultLocale")
fun Month.getFormattedName(): String {
- return getDisplayName(FULL, Locale.getDefault())
- .let {
- when (it) {
- "stycznia" -> "Styczeń"
- "lutego" -> "Luty"
- "marca" -> "Marzec"
- "kwietnia" -> "Kwiecień"
- "maja" -> "Maj"
- "czerwca" -> "Czerwiec"
- "lipca" -> "Lipiec"
- "sierpnia" -> "Sierpień"
- "września" -> "Wrzesień"
- "października" -> "Październik"
- "listopada" -> "Listopad"
- "grudnia" -> "Grudzień"
- else -> it
- }
- }.capitalize()
+ val formatter = SimpleDateFormat("LLLL", Locale.getDefault())
+
+ val date = now().withMonth(value)
+ return formatter.format(date.toInstant(ZoneOffset.UTC).toEpochMilli()).capitalize()
}
inline val LocalDate.nextSchoolDay: LocalDate
diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt
index 1af17e0e..e33059d8 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,9 +1,7 @@
-Wersja 0.24.3
-- naprawiliśmy odczytywanie wiadomości
-- naprawiliśmy niekończące się ładowanie w ocenach na drugim semestrze
-- naprawiliśmy ciemny motyw na MIUI 12
-- dodaliśmy automatyczne odświeżanie danych w aplikacji
-- naprawiliśmy wysyłanie wiadomości kiedy uczeń zalogowany był/jest przez konto ucznia i rodzica
-- dodaliśmy zakładkę lekcji dodatkowych (na górnym pasku w planie lekcji)
+Wersja 0.25.0
+- naprawiliśmy przełączanie semestrów przy przełączaniu uczniów
+- naprawiliśmy błąd przy odświeżaniu ocen gdy włączony był inny niż domyślny tryb liczenia średniej
+- dodaliśmy menadżer kont, gdzie można podejrzeć informacje o uczniu oraz zmienić pseudonim
+- zmieniliśmy ikonę planu lekcji oraz kolor animacji odświeżania w ciemnym motywie
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
diff --git a/app/src/main/res/drawable-anydpi-v24/ic_stat_timetable.xml b/app/src/main/res/drawable-anydpi-v24/ic_stat_timetable.xml
new file mode 100644
index 00000000..9efc0348
--- /dev/null
+++ b/app/src/main/res/drawable-anydpi-v24/ic_stat_timetable.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_timetable.png b/app/src/main/res/drawable-hdpi/ic_stat_timetable.png
index 201419d5..21095e29 100644
Binary files a/app/src/main/res/drawable-hdpi/ic_stat_timetable.png and b/app/src/main/res/drawable-hdpi/ic_stat_timetable.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_stat_timetable.png b/app/src/main/res/drawable-mdpi/ic_stat_timetable.png
index dcfe95f2..9147c409 100644
Binary files a/app/src/main/res/drawable-mdpi/ic_stat_timetable.png and b/app/src/main/res/drawable-mdpi/ic_stat_timetable.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_timetable.png b/app/src/main/res/drawable-xhdpi/ic_stat_timetable.png
index 7264bd92..96942b5a 100644
Binary files a/app/src/main/res/drawable-xhdpi/ic_stat_timetable.png and b/app/src/main/res/drawable-xhdpi/ic_stat_timetable.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_timetable.png b/app/src/main/res/drawable-xxhdpi/ic_stat_timetable.png
index 1fb37b09..510da8d5 100644
Binary files a/app/src/main/res/drawable-xxhdpi/ic_stat_timetable.png and b/app/src/main/res/drawable-xxhdpi/ic_stat_timetable.png differ
diff --git a/app/src/main/res/drawable/ic_account_details_family.xml b/app/src/main/res/drawable/ic_account_details_family.xml
new file mode 100644
index 00000000..363b4f58
--- /dev/null
+++ b/app/src/main/res/drawable/ic_account_details_family.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_about_homepage.xml b/app/src/main/res/drawable/ic_all_home.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_about_homepage.xml
rename to app/src/main/res/drawable/ic_all_home.xml
diff --git a/app/src/main/res/drawable/ic_school_phone.xml b/app/src/main/res/drawable/ic_all_phone.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_school_phone.xml
rename to app/src/main/res/drawable/ic_all_phone.xml
diff --git a/app/src/main/res/drawable/ic_main_timetable.xml b/app/src/main/res/drawable/ic_main_timetable.xml
index b8f5cb85..e33aae16 100644
--- a/app/src/main/res/drawable/ic_main_timetable.xml
+++ b/app/src/main/res/drawable/ic_main_timetable.xml
@@ -5,5 +5,5 @@
android:viewportHeight="24">
+ android:pathData="M14,12H15.5V14.82L17.94,16.23L17.19,17.53L14,15.69V12M4,2H18A2,2 0 0,1 20,4V10.1C21.24,11.36 22,13.09 22,15A7,7 0 0,1 15,22C13.09,22 11.36,21.24 10.1,20H4A2,2 0 0,1 2,18V4A2,2 0 0,1 4,2M4,15V18H8.67C8.24,17.09 8,16.07 8,15H4M4,8H10V5H4V8M18,8V5H12V8H18M4,13H8.29C8.63,11.85 9.26,10.82 10.1,10H4V13M15,10.15A4.85,4.85 0 0,0 10.15,15C10.15,17.68 12.32,19.85 15,19.85A4.85,4.85 0 0,0 19.85,15C19.85,12.32 17.68,10.15 15,10.15Z" />
diff --git a/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml b/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml
index 169395e9..ddb6d2f5 100644
--- a/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml
+++ b/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml
@@ -5,5 +5,5 @@
android:viewportHeight="24">
+ android:pathData="M6,1L6,3L5,3C3.89,3 3,3.89 3,5v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.89 2,-2L21,5C21,3.9 20.11,3 19,3L18,3L18,1L16,1L16,3L8,3L8,1ZM5,5L19,5L19,7L5,7ZM5,9L19,9L19,19L5,19ZM11,10v3L8,13v2h3v3h2v-3h3v-2h-3v-3z" />
diff --git a/app/src/main/res/layout/activity_send_message.xml b/app/src/main/res/layout/activity_send_message.xml
index e0ec52bb..10b581f7 100644
--- a/app/src/main/res/layout/activity_send_message.xml
+++ b/app/src/main/res/layout/activity_send_message.xml
@@ -194,9 +194,8 @@
tools:text="No messages" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_account.xml b/app/src/main/res/layout/dialog_account_quick.xml
similarity index 62%
rename from app/src/main/res/layout/dialog_account.xml
rename to app/src/main/res/layout/dialog_account_quick.xml
index d56de3a2..da31d31d 100644
--- a/app/src/main/res/layout/dialog_account.xml
+++ b/app/src/main/res/layout/dialog_account_quick.xml
@@ -8,23 +8,24 @@
+ android:orientation="vertical"
+ tools:ignore="UselessParent">
-
-
-
-
+ android:text="@string/account_quick_manager" />
diff --git a/app/src/main/res/layout/dialog_mobile_device.xml b/app/src/main/res/layout/dialog_mobile_device.xml
index 7b128605..680f5719 100644
--- a/app/src/main/res/layout/dialog_mobile_device.xml
+++ b/app/src/main/res/layout/dialog_mobile_device.xml
@@ -86,9 +86,8 @@
android:text="@string/all_close" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_account_details.xml b/app/src/main/res/layout/fragment_account_details.xml
new file mode 100644
index 00000000..1a0de147
--- /dev/null
+++ b/app/src/main/res/layout/fragment_account_details.xml
@@ -0,0 +1,273 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_attendance.xml b/app/src/main/res/layout/fragment_attendance.xml
index 39b00d30..8016081b 100644
--- a/app/src/main/res/layout/fragment_attendance.xml
+++ b/app/src/main/res/layout/fragment_attendance.xml
@@ -10,9 +10,8 @@
android:layout_height="match_parent"
android:layout_marginBottom="50dp">
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_login_recover.xml b/app/src/main/res/layout/fragment_login_recover.xml
index 205a4bea..629ed54d 100644
--- a/app/src/main/res/layout/fragment_login_recover.xml
+++ b/app/src/main/res/layout/fragment_login_recover.xml
@@ -5,9 +5,8 @@
android:layout_height="match_parent"
tools:context=".ui.modules.login.recover.LoginRecoverFragment">
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_teacher.xml b/app/src/main/res/layout/fragment_teacher.xml
index 484bf065..ef27d2c4 100644
--- a/app/src/main/res/layout/fragment_teacher.xml
+++ b/app/src/main/res/layout/fragment_teacher.xml
@@ -4,9 +4,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
-
-
-
+
+
@@ -21,7 +28,6 @@
android:id="@+id/accountHeaderType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="2dp"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
tools:text="Konto ucznia" />
diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml
index 9568b345..95fdb0e8 100644
--- a/app/src/main/res/layout/item_account.xml
+++ b/app/src/main/res/layout/item_account.xml
@@ -5,9 +5,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
android:orientation="horizontal"
- android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
+ android:paddingVertical="8dp"
tools:context=".ui.modules.account.AccountAdapter">
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/action_menu_account_details.xml b/app/src/main/res/menu/action_menu_account_details.xml
new file mode 100644
index 00000000..9ddc1e55
--- /dev/null
+++ b/app/src/main/res/menu/action_menu_account_details.xml
@@ -0,0 +1,11 @@
+
+
diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml
index e2e7bdac..fc01584b 100644
--- a/app/src/main/res/values-cs-rCZ/strings.xml
+++ b/app/src/main/res/values-cs-rCZ/strings.xml
@@ -17,7 +17,10 @@
Nová zpráva
Poznámky a úspěchy
Domácí úkoly
- Vyberte účet
+ Manažer účtů
+ Vyberte účet
+ Manažer účtů
+ Informace o žáku
Semestr %1$d, %2$d/%3$d
@@ -44,11 +47,11 @@
Neplatný e-mail
Místo e-mailu použijte přiřazené přihlašovací údaje
Neplatný symbol
- Student nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+
+ Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+
Toto pole je povinné
- Vybraný student je již přihlášen
+ Vybraný žák je již přihlášen
Symbol najdete na stránce deníku v Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUjistěte se, že jste na předchozí obrazovce nastavili správnou variantu deníku do pole Variace deníku UONET+. Wulkanowy v tuto chvíli nezjistí předškolní studenty
- Vyberte studenty, kteří se mají do aplikace přihlásit
+ Vyberte žáky, kteří se mají do aplikace přihlásit
Jiné možnosti
V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí docházky, ospravedlnění nepřítomnosti, absolvované lekce, informace o škole a prohlížení seznamu registrovaných zařízení
Tento režim zobrazuje stejná data, která se zobrazují na webových stránkách deníka
@@ -63,7 +66,7 @@
Zapomněl jsem své heslo
Obnovte svůj účet
Obnovit
- Student je již přihlášen
+ Žák je již přihlášen
Manažer účtů
Přihlásit se
@@ -84,17 +87,21 @@
Bez průměru
Předpovězeno: %1$s
Konečná: %1$s
- Celkem bodů
+ Součet bodů
Konečná známka
Předpokládaná známka
Vypočítaný průměr
Konečný průměr
- Souhrn
+ Shrnutí
Třída
Označit jako přečtené
Částečně
Semestr
Body
+ Vysvětlivky
+ Průměr: %1$s
+ Třída
+ Žák
- %d známka
- %d známky
@@ -139,7 +146,7 @@
Lekce
- Pokoj
+ Učebna
Skupina
Hodiny
Změny
@@ -164,10 +171,10 @@
Zobrazit další lekce
Žádné informace o dalších lekcích
- Souhrn docházky
+ Shrnutí docházky
Nepřítomen ze školních důvodů
Omluvená nepřítomnost
- Neomluvená absence
+ Neomluvená nepřítomnost
Osvobození
Omluvené zpoždění
Neomluvené zpoždění
@@ -189,7 +196,7 @@
Ospravedlnit
Účast
- Celkový
+ Společně
Tento týden žádné testy
Typ
@@ -215,7 +222,7 @@
Téma
Obsah
Zpráva úspěšně odeslána
- Message does not exist
+ Zpráva neexistuje
Musíte vybrat alespoň 1 příjemce
Obsah zprávy musí mít alespoň 3 znaky
@@ -337,12 +344,19 @@
Přidat účet
Odhlásit se
- Chcete se odhlásit z aktivního studenta?
- Odhlášení studentů
- Studentský účet
+ Chcete se odhlásit z aktivního žáka?
+ Odhlášení žáků
+ Žákův účet
Rodičovský účet
Režim Mobilního API
Hybridní režim
+ Upravit data
+ Manažer účtů
+ Vyberte žáka
+ Rodina
+ Kontakt
+ Údaje o adresách
+ Osobní údaje
Verze aplikace
Tvůrci
@@ -366,6 +380,30 @@
Avatar
Zobrazit více na GitHub
+
+ Žádné informace o žácích
+ Jméno
+ Druhé jméno
+ Pohlaví
+ Polské občanství
+ Rodinné jméno
+ Jména matky a otce
+ Telefon
+ Mobilní telefon
+ E-mail
+ Adresa bydliště
+ Registrovaná adresa
+ Korespondenční adresa
+ Příjmení a jméno
+ Stupeň příbuznosti
+ Adresa
+ Telefony
+ Muž
+ Žena
+ Příjmení
+
+ Přezdívka
+ Přidat přezdívku
Sdílejte protokoly
Obnovit
@@ -390,6 +428,9 @@
Další
Hledat
Hledat…
+ Ano
+ Ne
+ Uložit
Žádné lekce
Vybrat motiv
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 7ba35734..8d0bf3e9 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -17,7 +17,10 @@
neue Nachricht
Eintragen und Erfolgen
Hausaufgaben
- Wählen Sie ein Konto
+ Konten-Manager
+ Konto auswählen
+ Kontodetails
+ Schülerinfo
Semester %1$d, %2$d/%3$d
@@ -95,6 +98,10 @@
Partiell
Semester
Punkte
+ Legende
+ Durchschnitt: %1$s
+ Klasse
+ Schüler
- %d Note
- %d Noten
@@ -146,9 +153,9 @@
Abwesenheit
Ressourcen
- Additional lessons
- Show additional lessons
- No info about additional lessons
+ Zusätzliche Lektionen
+ Zusätzliche Lektionen anzeigen
+ Keine Infos zu zusätzlichen Lektionen
Übersicht über die Schulbesuch
Aus schulischen Gründen abwesend
@@ -199,7 +206,7 @@
Thema
Inhalt
Nachricht erfolgreich gesendet
- Message does not exist
+ Nachricht existiert nicht
Sie müssen mindestens 1 Empfänger auswählen.
Der Inhalt der Nachricht muss mindestens 3 Zeichen lang sein.
@@ -297,12 +304,19 @@
Konto hinzufügen
Abmelden
- Wollen Sie sich von einem aktiven Studenten abmelden?
+ Wollen Sie diesen Schüler abmelden?
Abmeldung von Student
Studentenkonto
Elternkonto
Mobiler API Modus
Hybrid Modus
+ Daten bearbeiten
+ Konten-Manager
+ Schüler auswählen
+ Familie
+ Kontakt
+ Wohnungsdetails
+ Persönliche Information
Version der App
Mitarbeiter
@@ -326,6 +340,30 @@
Benutzerbild
Sehen Sie mehr auf GitHub
+
+ Keine Informationen über Schüler
+ Name
+ Zweite Name
+ Geschlecht
+ Polnische Staatsbürgerschaft
+ Familienname
+ Namen von Mutter und Vater
+ Telefonnummer
+ Mobiltelefonnummer
+ E-Mail
+ Wohnadresse
+ Adresse der Registrierung
+ Korrespondenzadresse
+ Nachname und Vorname
+ Verwandtschaftsgrad
+ Adresse
+ Telefonnummern
+ Mann
+ Frau
+ Nachname
+
+ Nick
+ Nick hinzufügen
Logs teilen
Aktualisieren
@@ -350,6 +388,9 @@
Nächste
Suchen
Suchen…
+ Ja
+ Nein
+ Speichern
Keine Lektionen
Thema wählen
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
index 32f7cdf1..bf5cd769 100644
--- a/app/src/main/res/values-night/styles.xml
+++ b/app/src/main/res/values-night/styles.xml
@@ -7,6 +7,7 @@
- @color/timetable_change_light
- @color/colorErrorLight
- @color/colorDividerInverse
+ - @color/colorSwipeRefreshDark
- ?colorSurface
- ?android:textColorPrimary
- @android:color/black
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 19e312ba..2f849bc1 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -17,7 +17,10 @@
Nowa wiadomość
Uwagi i osiągnięcia
Zadania domowe
- Wybierz konto
+ Menadżer kont
+ Wybierz konto
+ Szczegóły konta
+ Informacje o uczniu
Semestr %1$d, %2$d/%3$d
@@ -95,6 +98,10 @@
Cząstkowe
Semestralne
Punkty
+ Legenda
+ Średnia: %1$s
+ Klasa
+ Uczeń
- %d ocena
- %d oceny
@@ -337,12 +344,19 @@
Dodaj konto
Wyloguj
- Czy chcesz wylogować aktualnego ucznia?
+ Czy chcesz wylogować tego ucznia?
Wylogowanie ucznia
Konto ucznia
Konto rodzica
Tryb API mobilne
Tryb hybrydowy
+ Edytuj dane
+ Menadżer kont
+ Wybierz ucznia
+ Rodzina
+ Kontakt
+ Dane adresowe
+ Dane osobowe
Wersja aplikacji
Twórcy
@@ -366,6 +380,30 @@
Awatar
Zobacz więcej na GitHub
+
+ Brak informacji o uczniu
+ Imię
+ Drugie imię
+ Płeć
+ Obywatelstwo polskie
+ Nazwisko rodowe
+ Imiona matki i ojca
+ Telefon
+ Telefon komórkowy
+ E-mail
+ Adres zamieszkania
+ Adres zameldowania
+ Adres korespondencyjny
+ Nazwisko i imię
+ Stopień pokrewieństwa
+ Adres
+ Telefony
+ Mężczyzna
+ Kobieta
+ Nazwisko
+
+ Pseudonim
+ Dodaj pseudonim
Udostępnij logi
Odśwież
@@ -390,6 +428,9 @@
Następny
Szukaj
Szukaj…
+ Tak
+ Nie
+ Zapisz
Brak lekcji
Wybierz motyw
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 9c4efa45..158c91bc 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -8,7 +8,7 @@
Тесты
Расписание
Настройки
- Другое
+ Ещё
О приложении
Просмотр журнала
Разработчики
@@ -17,7 +17,10 @@
Новое сообщение
Предупреждения и свершения
Домашние задания
- Выберите аккаунт
+ Менеджер аккаунтов
+ Выбор учетной записи
+ Данные аккаунта
+ Информация о студенте
%1$d семестр, %2$d/%3$d
@@ -27,10 +30,10 @@
Электронная почта
Логин, PESEL или электронная почта
Пароль
- Разновидностью бревна UONET+
- Mobile API
+ UONET + вариант регистрации
+ Мобильный API
Scraper
- Hybrid
+ Гибрид
Token
PIN
Ключ API
@@ -39,15 +42,15 @@
Слишком короткий пароль
Данные для входа неверны. Убедитесь, что в поле ниже выбран правильный вариант регистра UONET+
Неправильный PIN
- Неправильный token
- Токен просрочен
+ Неверный token
+ Token просрочен
Неверный адрес электронной почты
Используйте назначенный логин вместо электронной почты
- Неправильный symbol
+ Неправильный символ
Студент не найден. Подтвердите символ и выбранный вариант регистра UONET+
- Обязательное поле
+ Это обязательное поле
Данный ученик уже авторизован
- Этот символ можно найти на странице регистрации в Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nУбедитесь, что вы установили соответствующий вариант регистра в поле Разновидностью бревна UONET+ на предыдущем экране. Wulkanowy на данный момент не обнаруживает дошкольников
+ Этот символ можно найти на странице регистрации в Ученик → Телефонный доступ → Зарегистрируйте мобильное устройство.\n\nУбедитесь, что вы установили соответствующий вариант регистра в поле Разновидностью бревна UONET+ на предыдущем экране. Вулкановый на данный момент не обнаруживает дошкольников
Выберите учеников для авторизации в приложении
Другие варианты
В этом режиме не работают: счастливый номер, статистика класса по оценкам, статистика посещаемости и уроков, информация о школе и список зарегистрированных устройств
@@ -60,7 +63,7 @@
Отправить письмо
Опишите детали проблемы:
Убедитесь, что вы выбрали правильный вариант регистра UONET+!
- Забыли пароль?
+ Я забыл свой пароль
Восстановите свой аккаунт
Восстановить
Студент уже вошел в систему
@@ -68,7 +71,7 @@
Менеджер аккаунтов
Войти
Сеанс истёк
- Сеанс истёк, пожалуйста, авторизируйтесь ещё раз
+ Сеанс истёк, пожалуйста, войдите ещё раз
Оценка
Семестр %d
@@ -95,6 +98,10 @@
Частичные
За семестр
Баллы
+ Легенда
+ Средняя: %1$s
+ Класс
+ Студент
- %d оценка
- %d оценки
@@ -132,7 +139,7 @@
- Вы получили %1$d ожидаемых оценок
- - Вы получили %1$d финальную оценку
+ - Вы получили %1$d итоговою оценку
- Вы получили %1$d итоговых оценки
- Вы получили %1$d итоговых оценок
- Вы получили %1$d финальные оценки
@@ -143,7 +150,7 @@
Группа
Часы
Изменения
- Нет уроков в данный день
+ Нету уроков в данный день
%s мин
%s сек
%1$s осталось
@@ -160,9 +167,9 @@
Отсутствие
Ресурсы
- Additional lessons
- Show additional lessons
- No info about additional lessons
+ Дополнительные уроки
+ Показать дополнительные уроки
+ Нет информации о дополнительных уроках
Итоговая посещаемость
Отсутствие по школьным причинам
@@ -215,7 +222,7 @@
Тема
Текст
Сообщение успешно отправлено
- Message does not exist
+ Сообщения не существует
Вы должны выбрать как минимум одного получателя
Текст сообщения должен содержать как минимум 3 знака
@@ -237,7 +244,7 @@
- Вы получили %1$d новых сообщений
- Нет данных о предупреждениях
+ Нет информации о заметках
Баллы
- %d предупреждение
@@ -297,19 +304,19 @@
Нет домашних заданий
- сделанный
- Не сделано
+ Отметить как выполненное
+ Отметить как невыполненное
Вложения
Счастливый номер
- Сегодняшний счастливый номер
+ Сегодняшний счастливый номер это
Нет данных о счастливом номере
Сегодняшний счастливый номер
- Сегодняшний счастливый номер: %d
+ Сегодняшний счастливый номер это: %d
Мобильные устройства
Нет устройств
- Удалить
+ Отменить регистрацию
Устройство удалено
QR-код
Token
@@ -323,8 +330,8 @@
Название школы
Адрес школы
Телефон
- Директор
- Педагог
+ Имя директора
+ Имя педагога
Показать на карте
Позвонить
@@ -343,10 +350,17 @@
Профиль родителя
Режим Mobile API
Гибридный режим
+ Изменить данные
+ Менеджер аккаунтов
+ Выберите Студента
+ Семья
+ Контакт
+ Детали проживания
+ Персональные данные
Версия приложения
Разработчики
- Список разработчиков \"Wulkanowy\"
+ Список разработчиков \"Вулкановый\"
Возникла ошибка?
Сообщить о ошибке
FAQ
@@ -366,6 +380,30 @@
Aватар
Страница проекта на GitHub
+
+ Нет информации о студенте
+ Имя
+ Фамилия
+ Пол
+ Польское гражданство
+ Фамилия
+ Имена матери и отца
+ Телефон
+ Сотовый телефон
+ Эл. почта
+ Адрес проживания
+ Адрес регистрации
+ Адрес переписки
+ Фамилия и имя
+ Степень родства
+ Адрес
+ Телефоны
+ Муж
+ Женская
+ Фамилия
+
+ Ник
+ Добавить ник
Поделиться логами
Обновить
@@ -390,6 +428,9 @@
Следующий
Поиск
Поиск…
+ Да
+ Нет
+ Сохранить
Нет уроков
Выбрать тему
diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml
index 54012b1f..b5c0116e 100644
--- a/app/src/main/res/values-sk-rSK/strings.xml
+++ b/app/src/main/res/values-sk-rSK/strings.xml
@@ -17,7 +17,10 @@
Nová správa
Poznámky a úspechy
Domáce úlohy
- Vyberte účet
+ Accounts manager
+ Select account
+ Account details
+ Student info
Semester %1$d, %2$d/%3$d
@@ -44,11 +47,11 @@
Neplatný e-mail
Namiesto e-mailu použite priradené prihlasovacie údaje
Neplatný symbol
- Študent nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+
+ Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+
Toto pole je povinné
- Vybraný študent je už prihlásený
+ Vybraný žiak už je prihlásený
The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous screen. Wulkanowy does not detect pre-school students at the moment
- Vyberte studenty, kteří se mají do aplikace přihlásit
+ Vyberte žiakov, ktorí sa majú do aplikácie prihlásiť
Iné možnosti
In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices
This mode displays the same data as it appears on the register website
@@ -84,17 +87,21 @@
Bez priemeru
Predpovedané: %1$s
Konečná: %1$s
- Celkom bodov
+ Súčet bodov
Konečná známka
Predpokladaná známka
Vypočítaný priemer
Konečný priemer
- Súhrn
+ Zhrnutie
Trieda
Označiť ako prečítané
- Čiastočne
+ Čiastočné
Semester
Body
+ Legenda
+ Average: %1$s
+ Trieda
+ Žiák
- %d známka
- %d známky
@@ -109,27 +116,27 @@
- Nová predpokladaná známka
- - Nové predpovedanej známky
- - Nové predpovedanej známky
- - Nové predpovedanej známky
+ - Nové predpokladané známky
+ - Nové predpokladané známky
+ - Nové predpokladané známky
- Nová konečná známka
- - Nové konečnej známky
- - Nové konečnej známky
- - Nové konečnej známky
+ - Nové konečné známky
+ - Nové konečné známky
+ - Nové konečné známky
- Máte %1$d novú známku
- Máte %1$d nové známky
- - Máte %1$d nové známky
- - Máte %1$d nové známky
+ - Máte %1$d nových známok
+ - Máte %1$d nových známok
- Máte %1$d novú predpokladanú známku
- Máte %1$d nové predpokladané známky
- - Máte %1$d nové predpokladané známky
- - Máte %1$d nové predpokladané známky
+ - Máte %1$d nových predpokladaných známok
+ - Máte %1$d nových predpokladaných známok
- Máte %1$d novú konečnú známku
@@ -139,7 +146,7 @@
Lekcia
- Room
+ Učebňa
Skupina
Hodiny
Zmeny
@@ -158,9 +165,9 @@
No info about completed lessons
Téma
Neprítomnosť
- Resources
+ Zdroje
- Additional lessons
+ Ďalší lekcie
Show additional lessons
No info about additional lessons
@@ -171,11 +178,11 @@
Exemption
Excused lateness
Unexcused lateness
- Present
+ Prítomnosť
Deleted
- Unknown
+ Neznámy
Number of lesson
- No entries
+ Žiadne položky
- %1$d absence
- %1$d absences
@@ -183,7 +190,7 @@
- %1$d absences
Absence reason (optional)
- Send
+ Poslať
Absence excused successfully!
You must select at least one absence!
Excuse
@@ -337,12 +344,19 @@
Add account
Logout
- Do you want to log out of an active student?
+ Do you want to log out this student?
Student logout
Student account
Parent account
Mobile API mode
Hybrid mode
+ Edit data
+ Accounts manager
+ Select student
+ Family
+ Contact
+ Residence details
+ Personal information
App version
Contributors
@@ -366,6 +380,30 @@
Avatar
See more on GitHub
+
+ No info about student
+ Name
+ Second name
+ Gender
+ Polish citizenship
+ Family name
+ Mother\'s and father\'s names
+ Phone
+ Cellphone
+ E-mail
+ Address of residence
+ Address of registration
+ Correspondence address
+ Surname and first name
+ Degree of kinship
+ Address
+ Phones
+ Male
+ Female
+ Last name
+
+ Nick
+ Add nick
Share logs
Refresh
@@ -390,6 +428,9 @@
Next
Search
Search…
+ Yes
+ No
+ Save
No lessons
Choose theme
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index a1511d9f..3c4b4436 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -17,11 +17,14 @@
Нове повідомлення
Нотатки та досягнення
Домашні завдання
- Оберіть аккаунт
+ Менеджер аккаунтів
+ Виберіть обліковий запис
+ Деталі облікового запису
+ Інформація про учня
%1$d семестр, %2$d/%3$d
- Авторизуйтеся за допомогою аккаунта учня або батька
+ Авторизуйтеся за допомогою аккаунта учня або батьків
Введіть символ зі сторінки реєстру
Ім\'я користувача
Електронна пошта
@@ -43,11 +46,11 @@
Минув термін дії токену
Недійсна адреса електронної пошти
Використовуйте призначений логін замість електронної пошти
- Неправильний symbol
+ Неправильний симбвол
Студента не знайдено Перевірте символ та обраний варіант реєстру UONET+
Обов\'язкове поле
Даного учня вже авторизовано
- Символ можна знайти на сторінці реєстру в Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne .\n\nПереконайтесь, що ви встановили відповідний варіант реєстру в полі UONET + варіант реєстрації на попередньому екрані. На даний момент Wulkanowy не виявляє учнів дошкільних закладів
+ Символ можна знайти на сторінці реєстру в Учень → Мобільний доступ → Додайте мобільне приладдя .\n\nПереконайтесь, що ви встановили відповідний варіант реєстру в полі UONET + варіант реєстрації на попередньому екрані. На даний момент Wulkanowy не виявляє учнів дошкільних закладів
Виберіть учнів для авторизації в додатку
Інші варіанти
У цьому режимі не працюють: щасливий номер, статистика класу по оцінкам, статистика відвідуваності і уроків, інформація про школу і список зареєстрованних пристроїв
@@ -61,7 +64,7 @@
Опишіть деталі помилки:
Переконайтеся, що ви вибрали правильний варіант реєстрації UONET+!
Забули пароль?
- Відновіть свій аккаунт
+ Відновіть свій обліковий запис
Відновити
Учень вже увійшов до системи
@@ -73,15 +76,15 @@
Оцінка
%d семестр
Змінити семестр
- Брак оцінок
+ Оцінки відсутні
Вартість
Вартість: %s
Коментар
- Брак нових оцінок
+ Нові оцінки відсутні
Кількість нових оцінок: %1$d
Середня оцінка: %1$.2f
- точок: %s
- Брак середньої оцінки
+ Балів: %s
+ Відсутність середньої оцінки
Очікувана оцінка: %1$s
Підсумкова оцінка: %1$s
Сума балів
@@ -95,6 +98,10 @@
Поточні
Семестрові
Бали
+ Умовні позначення
+ Середня оцінка: %1$s
+ Клас
+ Учень
- %d оцінка
- %d оцінки
@@ -160,9 +167,9 @@
Відсутність
Ресурси
- Additional lessons
- Show additional lessons
- No info about additional lessons
+ Додаткові уроки
+ Показати додаткові уроки
+ Немає інформації про додаткових уроків
Підсумок відвідуваності
Відсутність зі шкільних причин
@@ -215,7 +222,7 @@
Тема
Зміст
Повідомлення було успішно відправлено
- Message does not exist
+ Такого повідомлення не існує
Необхідно обрати принаймні 1 адресата
Зміст повідомлення мусить складатися принаймні з 3 знаків
@@ -343,6 +350,13 @@
Головний рахунок
Режим мобільного API
Гібридний режим
+ Редагувати дані
+ Менеджер аккаунтів
+ Виберіть учня
+ Сім\'я
+ Контакт
+ Деталі проживання
+ Особиста інформація
Версія додатка
Розробники
@@ -366,6 +380,30 @@
Аватар
Сторінка проекту на GitHub
+
+ Брак інформації про учня
+ Ім\'я
+ Друге ім\'я
+ Стать
+ Польське громадянство
+ Прізвище
+ Імена батька і матері
+ Номер телефону
+ Мобільний телефон
+ Електронна пошта
+ Місця проживання
+ Адреса реєстрації
+ Адреса для кореспонденції
+ Прізвище та ім\'я
+ Ступінь спорідненості
+ Адреса
+ Телефони
+ Чоловіча
+ Жінка
+ Прізвище
+
+ Псевдонім
+ Додати псевдонім
Поділитися логами
Оновити
@@ -390,6 +428,9 @@
Наступний
Пошук
Пошук…
+ Так
+ Ні
+ Зберегти
Брак уроків
Увібрати тему
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 49cdd5b7..49ef39ab 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -1,5 +1,6 @@
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 36dee181..99456744 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -12,6 +12,9 @@
#1f000000
#1fffffff
+ #ff383838
+ #ffffffff
+
#a0c431
#d43f3f
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9d707d09..e41f2f28 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -17,7 +17,10 @@
New message
Notes and achievements
Homework
- Choose account
+ Accounts manager
+ Select account
+ Account details
+ Student info
@@ -105,6 +108,10 @@
Partial
Semester
Points
+ Legend
+ Average: %1$s
+ Class
+ Student
- %d grade
- %d grades
@@ -340,12 +347,19 @@
Add account
Logout
- Do you want to log out of an active student?
+ Do you want to log out this student?
Student logout
Student account
Parent account
Mobile API mode
Hybrid mode
+ Edit data
+ Accounts manager
+ Select student
+ Family
+ Contact
+ Residence details
+ Personal information
@@ -378,6 +392,34 @@
See more on GitHub
+
+ No info about student
+ Name
+ Second name
+ Gender
+ Polish citizenship
+ Family name
+ Mother\'s and father\'s names
+ Phone
+ Cellphone
+ E-mail
+ Address of residence
+ Address of registration
+ Correspondence address
+ Surname and first name
+ Degree of kinship
+ Address
+ Phones
+ Male
+ Female
+ Last name
+
+
+
+ Nick
+ Add nick
+
+
Share logs
Refresh
@@ -406,6 +448,9 @@
Next
Search
Search…
+ Yes
+ No
+ Save
@@ -504,4 +549,5 @@
An unexpected error occurred
Feature disabled by your school
Feature not available. Login in a mode other than Mobile API
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 788f081e..cf587cbf 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -10,6 +10,7 @@
- @color/timetable_change_dark
- @color/colorError
- @color/colorDivider
+ - @color/colorSwipeRefresh
- ?android:textColorPrimary
- @style/PreferenceThemeOverlay
diff --git a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
index ba29e1cb..b6532579 100644
--- a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
+++ b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt
@@ -20,7 +20,7 @@ class AnalyticsHelper @Inject constructor(
params.forEach {
if (it.second == null) return@forEach
when (it.second) {
- is String, is String? -> putString(it.first, it.second as String)
+ is String, is String? -> putString(it.first, it.second.toString())
is Int, is Int? -> putInt(it.first, it.second as Int)
is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
}
diff --git a/app/src/play/java/io/github/wulkanowy/utils/UpdateHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/UpdateHelper.kt
index 7586a21a..1dd3bc68 100644
--- a/app/src/play/java/io/github/wulkanowy/utils/UpdateHelper.kt
+++ b/app/src/play/java/io/github/wulkanowy/utils/UpdateHelper.kt
@@ -17,6 +17,7 @@ import com.google.android.play.core.install.model.UpdateAvailability.DEVELOPER_T
import com.google.android.play.core.install.model.UpdateAvailability.UPDATE_AVAILABLE
import com.google.android.play.core.ktx.isFlexibleUpdateAllowed
import com.google.android.play.core.ktx.isImmediateUpdateAllowed
+import com.google.android.play.core.ktx.updatePriority
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import timber.log.Timber
@@ -24,40 +25,57 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class UpdateHelper @Inject constructor(@ApplicationContext private val context: Context) {
+class UpdateHelper @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val analyticsHelper: AnalyticsHelper,
+) {
lateinit var messageContainer: View
- companion object {
- const val IN_APP_UPDATE_REQUEST_CODE = 1721
-
- const val DAYS_FOR_FLEXIBLE_UPDATE = 7
- const val HIGH_PRIORITY_UPDATE = 4
- }
-
private val appUpdateManager by lazy { AppUpdateManagerFactory.create(context) }
private val flexibleUpdateListener = InstallStateUpdatedListener { state ->
when (state.installStatus()) {
- PENDING -> Toast.makeText(context, R.string.update_download_started, Toast.LENGTH_SHORT).show()
+ PENDING -> Toast.makeText(context, R.string.update_download_started, Toast.LENGTH_SHORT)
+ .show()
DOWNLOADED -> popupSnackBarForCompleteUpdate()
else -> Timber.d("Update state: ${state.installStatus()}")
}
}
private inline val AppUpdateInfo.isImmediateUpdateAvailable: Boolean
- get() = updateAvailability() == UPDATE_AVAILABLE && isImmediateUpdateAllowed &&
- updatePriority() >= HIGH_PRIORITY_UPDATE
+ get() {
+ val days = clientVersionStalenessDays() ?: 0
+ val isUpdatePriorityAllowUpdate = when (updatePriority) {
+ 5 -> true
+ 4 -> days > 7
+ 3 -> days > 30
+ else -> false
+ }
+
+ return updateAvailability() == UPDATE_AVAILABLE && isImmediateUpdateAllowed && isUpdatePriorityAllowUpdate
+ }
private inline val AppUpdateInfo.isFlexibleUpdateAvailable: Boolean
- get() = updateAvailability() == UPDATE_AVAILABLE && isFlexibleUpdateAllowed &&
- clientVersionStalenessDays() ?: 0 >= DAYS_FOR_FLEXIBLE_UPDATE
+ get() {
+ val days = clientVersionStalenessDays() ?: 0
+ val isUpdatePriorityAllowUpdate = when (updatePriority) {
+ 4, 3, 2 -> true
+ 1 -> days >= 7
+ 0 -> false
+ else -> false
+ }
+
+ return updateAvailability() == UPDATE_AVAILABLE && isFlexibleUpdateAllowed && isUpdatePriorityAllowUpdate
+ }
fun checkAndInstallUpdates(activity: Activity) {
Timber.d("Checking for updates...")
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
when {
- appUpdateInfo.isImmediateUpdateAvailable -> startUpdate(activity, appUpdateInfo, IMMEDIATE)
+ appUpdateInfo.isImmediateUpdateAvailable -> {
+ startUpdate(activity, appUpdateInfo, IMMEDIATE)
+ }
appUpdateInfo.isFlexibleUpdateAvailable -> {
appUpdateManager.registerListener(flexibleUpdateListener)
startUpdate(activity, appUpdateInfo, FLEXIBLE)
@@ -68,15 +86,20 @@ class UpdateHelper @Inject constructor(@ApplicationContext private val context:
}
private fun startUpdate(activity: Activity, appUpdateInfo: AppUpdateInfo, updateType: Int) {
+ Timber.d("Start update ($updateType): $appUpdateInfo")
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo, updateType, activity, IN_APP_UPDATE_REQUEST_CODE
)
}
fun onActivityResult(requestCode: Int, resultCode: Int) {
- if (requestCode == IN_APP_UPDATE_REQUEST_CODE && resultCode != RESULT_OK) {
- Timber.e("Update failed! Result code: $resultCode")
- Toast.makeText(context, R.string.update_failed, Toast.LENGTH_LONG).show()
+ if (requestCode == IN_APP_UPDATE_REQUEST_CODE) {
+ if (resultCode != RESULT_OK) {
+ Timber.i("Update failed! Result code: $resultCode")
+ Toast.makeText(context, R.string.update_failed, Toast.LENGTH_LONG).show()
+ }
+
+ analyticsHelper.logEvent("inapp_update", "code" to resultCode)
}
}
@@ -87,14 +110,23 @@ class UpdateHelper @Inject constructor(@ApplicationContext private val context:
when {
DOWNLOADED == info.installStatus() -> popupSnackBarForCompleteUpdate()
DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS == info.updateAvailability() -> {
- startUpdate(activity, info, if (info.isImmediateUpdateAvailable) IMMEDIATE else FLEXIBLE)
+ startUpdate(
+ activity = activity,
+ appUpdateInfo = info,
+ updateType = if (info.isImmediateUpdateAvailable) IMMEDIATE else FLEXIBLE
+ )
}
}
}
}
private fun popupSnackBarForCompleteUpdate() {
- Snackbar.make(messageContainer, R.string.update_download_success, Snackbar.LENGTH_INDEFINITE).apply {
+ Timber.d("Show snackbar with update complete")
+ Snackbar.make(
+ messageContainer,
+ R.string.update_download_success,
+ Snackbar.LENGTH_INDEFINITE
+ ).apply {
setAction(R.string.update_download_success_button) {
appUpdateManager.completeUpdate()
appUpdateManager.unregisterListener(flexibleUpdateListener)
@@ -102,4 +134,9 @@ class UpdateHelper @Inject constructor(@ApplicationContext private val context:
show()
}
}
+
+ private companion object {
+
+ private const val IN_APP_UPDATE_REQUEST_CODE = 1721
+ }
}
diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt
index 22669323..415f6e0a 100644
--- a/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt
@@ -35,7 +35,9 @@ class StudentTest {
@Test
fun testRemoteAll() {
- coEvery { mockSdk.getStudentsFromScrapper(any(), any(), any(), any()) } returns listOf(getStudent("test"))
+ coEvery { mockSdk.getStudentsFromScrapper(any(), any(), any(), any()) } returns listOf(
+ getStudent("test")
+ )
val students = runBlocking { studentRepository.getStudentsScrapper("", "", "http://fakelog.cf", "") }
assertEquals(1, students.size)
diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt
index 4479965c..0da02cf5 100644
--- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt
@@ -15,6 +15,7 @@ import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every
import io.mockk.impl.annotations.MockK
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.toList
@@ -41,7 +42,29 @@ class GradeAverageProviderTest {
private lateinit var gradeAverageProvider: GradeAverageProvider
- private val student = Student("", "", "", "SCRAPPER", "", "", false, "", "", "", 101, 0, "", "", "", "", "", "", 1, true, LocalDateTime.now())
+ private val student = Student(
+ scrapperBaseUrl = "",
+ mobileBaseUrl = "",
+ loginType = "",
+ loginMode = "SCRAPPER",
+ certificateKey = "",
+ privateKey = "",
+ isParent = false,
+ email = "",
+ password = "",
+ symbol = "",
+ studentId = 101,
+ userLoginId = 0,
+ userName = "",
+ studentName = "",
+ schoolSymbol = "",
+ schoolShortName = "",
+ schoolName = "",
+ className = "",
+ classId = 1,
+ isCurrent = true,
+ registrationDate = LocalDateTime.now()
+ )
private val semesters = mutableListOf(
getSemesterEntity(10, 21, of(2019, 1, 31), of(2019, 6, 23)),
@@ -130,6 +153,51 @@ class GradeAverageProviderTest {
assertEquals(3.5, items[1].data?.single { it.subject == "Język polski" }!!.average, .0) // from details and after set custom plus/minus
}
+ @Test
+ fun `calc all year semester average with delayed emit`(){
+ every { preferencesRepository.gradeAverageForceCalc } returns true
+ every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR
+
+ coEvery { semesterRepository.getSemesters(student) } returns semesters
+ coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flow {
+ emit(Resource.loading())
+ delay(1000)
+ emit(Resource.success(secondGradeWithModifier to secondSummariesWithModifier))
+ }
+ coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flow {
+ emit(Resource.loading())
+ emit(Resource.success(secondGradeWithModifier to secondSummariesWithModifier))
+ }
+
+ val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, false).toList() }
+
+ with(items[0]) {
+ assertEquals(Status.LOADING, status)
+ assertEquals(null, data)
+ }
+ with(items[1]) {
+ assertEquals(Status.SUCCESS, status)
+ assertEquals(1, data!!.size)
+ }
+
+ assertEquals(3.5, items[1].data?.single { it.subject == "Język polski" }!!.average, .0) // from details and after set custom plus/minus
+ }
+
+ @Test
+ fun `calc both semesters average with grade without grade in second semester`() {
+ every { preferencesRepository.gradeAverageForceCalc } returns true
+ every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS
+
+ coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier }
+ coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flowWithResource {
+ listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)) to listOf(getSummary(semesters[2].semesterId, "Język polski", 2.5))
+ }
+
+ val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, false).getResult() }
+
+ assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0)
+ }
+
@Test
fun `force calc average on no grades`() {
every { preferencesRepository.gradeAverageForceCalc } returns true
diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt
index b475584b..31e3e198 100644
--- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt
@@ -99,8 +99,32 @@ class LoginFormPresenterTest {
@Test
fun loginTest() {
- val studentTest = Student(email = "test@", password = "123", scrapperBaseUrl = "https://fakelog.cf/", loginType = "AUTO", studentName = "", schoolSymbol = "", schoolName = "", studentId = 0, classId = 1, isCurrent = false, symbol = "", registrationDate = now(), className = "", mobileBaseUrl = "", privateKey = "", certificateKey = "", loginMode = "", userLoginId = 0, schoolShortName = "", isParent = false, userName = "")
- coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf(StudentWithSemesters(studentTest, emptyList()))
+ val studentTest = Student(
+ email = "test@",
+ password = "123",
+ scrapperBaseUrl = "https://fakelog.cf/",
+ loginType = "AUTO",
+ studentName = "",
+ schoolSymbol = "",
+ schoolName = "",
+ studentId = 0,
+ classId = 1,
+ isCurrent = false,
+ symbol = "",
+ registrationDate = now(),
+ className = "",
+ mobileBaseUrl = "",
+ privateKey = "",
+ certificateKey = "",
+ loginMode = "",
+ userLoginId = 0,
+ schoolShortName = "",
+ isParent = false,
+ userName = ""
+ )
+ coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf(
+ StudentWithSemesters(studentTest, emptyList())
+ )
every { loginFormView.formUsernameValue } returns "@"
every { loginFormView.formPassValue } returns "123456"
diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt
index 4b977682..9e34235e 100644
--- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt
@@ -38,7 +38,31 @@ class LoginStudentSelectPresenterTest {
private lateinit var presenter: LoginStudentSelectPresenter
- private val testStudent by lazy { Student(email = "test", password = "test123", scrapperBaseUrl = "https://fakelog.cf", loginType = "AUTO", symbol = "", isCurrent = false, studentId = 0, schoolName = "", schoolSymbol = "", classId = 1, studentName = "", registrationDate = now(), className = "", loginMode = "", certificateKey = "", privateKey = "", mobileBaseUrl = "", schoolShortName = "", userLoginId = 1, isParent = false, userName = "") }
+ private val testStudent by lazy {
+ Student(
+ email = "test",
+ password = "test123",
+ scrapperBaseUrl = "https://fakelog.cf",
+ loginType = "AUTO",
+ symbol = "",
+ isCurrent = false,
+ studentId = 0,
+ schoolName = "",
+ schoolSymbol = "",
+ classId = 1,
+ studentName = "",
+ registrationDate = now(),
+ className = "",
+ loginMode = "",
+ certificateKey = "",
+ privateKey = "",
+ mobileBaseUrl = "",
+ schoolShortName = "",
+ userLoginId = 1,
+ isParent = false,
+ userName = ""
+ )
+ }
private val testException by lazy { RuntimeException("Problem") }
@@ -64,8 +88,24 @@ class LoginStudentSelectPresenterTest {
@Test
fun onSelectedStudentTest() {
- coEvery { studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList()))) } returns listOf(1L)
- coEvery { studentRepository.switchStudent(StudentWithSemesters(testStudent, emptyList())) } just Runs
+ coEvery {
+ studentRepository.saveStudents(
+ listOf(
+ StudentWithSemesters(
+ testStudent,
+ emptyList()
+ )
+ )
+ )
+ } returns listOf(1L)
+ coEvery {
+ studentRepository.switchStudent(
+ StudentWithSemesters(
+ testStudent,
+ emptyList()
+ )
+ )
+ } just Runs
every { loginStudentSelectView.openMainView() } just Runs
presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false)
presenter.onSignIn()
@@ -77,7 +117,16 @@ class LoginStudentSelectPresenterTest {
@Test
fun onSelectedStudentErrorTest() {
- coEvery { studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList()))) } throws testException
+ coEvery {
+ studentRepository.saveStudents(
+ listOf(
+ StudentWithSemesters(
+ testStudent,
+ emptyList()
+ )
+ )
+ )
+ } throws testException
coEvery { studentRepository.logoutStudent(testStudent) } just Runs
presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false)
presenter.onSignIn()
diff --git a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt
index 3336c0d9..0ffbf78e 100644
--- a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt
@@ -50,15 +50,28 @@ class TimeExtensionTest {
fun monthNameTest() {
Locale.setDefault(Locale.forLanguageTag("PL"))
assertEquals("Styczeń", JANUARY.getFormattedName())
- Locale.setDefault(Locale.forLanguageTag("EN"))
+
+ Locale.setDefault(Locale.forLanguageTag("CS"))
+ assertEquals("Leden", JANUARY.getFormattedName())
+
+ Locale.setDefault(Locale.ENGLISH)
assertEquals("January", JANUARY.getFormattedName())
+
+ Locale.setDefault(Locale.forLanguageTag("DE"))
+ assertEquals("Januar", JANUARY.getFormattedName())
+
+ Locale.setDefault(Locale.forLanguageTag("RU"))
+ assertEquals("Январь", JANUARY.getFormattedName())
+
+ Locale.setDefault(Locale.forLanguageTag("UK"))
+ assertEquals("Січень", JANUARY.getFormattedName())
}
@Test
fun weekDayNameTest() {
Locale.setDefault(Locale.forLanguageTag("PL"))
assertEquals("poniedziałek", of(2018, 10, 1).weekDayName)
- Locale.setDefault(Locale.forLanguageTag("EN"))
+ Locale.setDefault(Locale.ENGLISH)
assertEquals("Monday", of(2018, 10, 1).weekDayName)
}
diff --git a/build.gradle b/build.gradle
index 81794ab6..5ab0182b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,8 +1,8 @@
buildscript {
ext {
kotlin_version = '1.4.21'
- about_libraries = '8.6.7'
- hilt_version = "2.30.1-alpha"
+ about_libraries = '8.7.0'
+ hilt_version = "2.31.2-alpha"
}
repositories {
mavenCentral()
@@ -15,11 +15,11 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:4.1.2'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
- classpath 'com.google.gms:google-services:4.3.4'
- classpath 'com.huawei.agconnect:agcp:1.4.2.301'
+ classpath 'com.google.gms:google-services:4.3.5'
+ classpath 'com.huawei.agconnect:agcp:1.5.0.200'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
classpath "com.github.triplet.gradle:play-publisher:2.8.0"
- classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1"
+ classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"
}
diff --git a/gradle.properties b/gradle.properties
index 5741ecfc..998e3195 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -7,10 +7,13 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
android.enableJetifier=true
android.useAndroidX=true
-org.gradle.jvmargs=-Xmx1536m
+kotlin.code.style=official
kapt.incremental.apt=true
+kapt.use.worker.api=true
+kapt.include.compile.classpath=false
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 33682bbb..1c4bcc29 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists