diff --git a/.travis.yml b/.travis.yml
index 788aeffa..a2405808 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,7 +14,7 @@ cache:
branches:
only:
- develop
- - 0.18.3
+ - 0.19.0
android:
licenses:
diff --git a/app/build.gradle b/app/build.gradle
index 498c7a37..621e5a4f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -17,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17
targetSdkVersion 29
- versionCode 62
- versionName "0.18.3"
+ versionCode 63
+ versionName "0.19.0"
multiDexEnabled true
resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -77,8 +77,8 @@ android {
}
}
- viewBinding {
- enabled = true
+ buildFeatures {
+ viewBinding = true
}
lintOptions {
@@ -124,14 +124,14 @@ configurations.all {
}
dependencies {
- implementation "io.github.wulkanowy:sdk:0.18.3"
+ implementation "io.github.wulkanowy:sdk:0.19.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.appcompat:appcompat:1.2.0-rc01"
implementation "androidx.appcompat:appcompat-resources:1.1.0"
- implementation "androidx.fragment:fragment-ktx:1.2.4"
+ implementation "androidx.fragment:fragment-ktx:1.2.5"
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.multidex:multidex:2.0.1"
@@ -142,7 +142,7 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.1.0"
- implementation "com.github.wulkanowy:material-chips-input:2.0.1"
+ 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"
@@ -180,12 +180,13 @@ dependencies {
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
implementation "io.coil-kt:coil:0.11.0"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
+ implementation 'me.xdrop:fuzzywuzzy:1.3.1'
- playImplementation 'com.google.firebase:firebase-analytics:17.4.2'
+ playImplementation 'com.google.firebase:firebase-analytics:17.4.3'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.7"
playImplementation 'com.google.firebase:firebase-messaging:20.2.0'
- playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0'
+ playImplementation 'com.google.firebase:firebase-crashlytics:17.0.1'
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json
index e99a11d4..474824df 100644
--- a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json
@@ -1741,4 +1741,4 @@
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd101f5a26a024f62e6fee161e421b882')"
]
}
-}
\ No newline at end of file
+}
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json
new file mode 100644
index 00000000..21005f9c
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json
@@ -0,0 +1,1768 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 26,
+ "identityHash": "43f8777ffe95a5a2850ee981db3dbe2e",
+ "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, `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": "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": "GradesStatistics",
+ "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, `grade` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `is_semester` 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": "grade",
+ "columnName": "grade",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amount",
+ "columnName": "amount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "is_semester",
+ "affinity": "INTEGER",
+ "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": "Messages",
+ "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, `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, `content` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` 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": "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": "content",
+ "columnName": "content",
+ "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": "unreadBy",
+ "columnName": "unread_by",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "readBy",
+ "columnName": "read_by",
+ "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": "realId",
+ "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": "studentId",
+ "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": "studentId",
+ "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": []
+ }
+ ],
+ "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, '43f8777ffe95a5a2850ee981db3dbe2e')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt b/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt
index 6f9406f6..da4284b0 100644
--- a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt
+++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt
@@ -120,23 +120,23 @@ class Migration13Test : AbstractMigrationTest() {
assertEquals(2, first.diaryId)
}
- getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let {
- assertTrue { it.single { it.second }.second }
- assertEquals(1970, it[0].first.schoolYear)
- assertEquals(of(1970, 1, 1), it[0].first.end)
- assertEquals(of(1970, 1, 1), it[0].first.start)
- assertFalse(it[0].second)
- assertFalse(it[1].second)
- assertFalse(it[2].second)
- assertTrue(it[3].second)
+ getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { semesters ->
+ assertTrue { semesters.single { it.second }.second }
+ assertEquals(1970, semesters[0].first.schoolYear)
+ assertEquals(of(1970, 1, 1), semesters[0].first.end)
+ assertEquals(of(1970, 1, 1), semesters[0].first.start)
+ assertFalse(semesters[0].second)
+ assertFalse(semesters[1].second)
+ assertFalse(semesters[2].second)
+ assertTrue(semesters[3].second)
}
- getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let {
- assertTrue { it.single { it.second }.second }
- assertFalse(it[0].second)
- assertFalse(it[1].second)
- assertFalse(it[2].second)
- assertTrue(it[3].second)
+ getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { semesters ->
+ assertTrue { semesters.single { it.second }.second }
+ assertFalse(semesters[0].second)
+ assertFalse(semesters[1].second)
+ assertFalse(semesters[2].second)
+ assertTrue(semesters[3].second)
}
}
diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt
index c7ede6ae..cdfc524a 100644
--- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt
+++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt
@@ -10,6 +10,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.of
import kotlin.test.assertEquals
@@ -35,9 +36,18 @@ class AttendanceLocalTest {
@Test
fun saveAndReadTest() {
attendanceLocal.saveAttendance(listOf(
- Attendance(1, 2, 3, of(2018, 9, 10), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name),
- Attendance(1, 2, 3, of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name),
- Attendance(1, 2, 3, of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name)
+ getAttendanceEntity(
+ of(2018, 9, 10),
+ SentExcuseStatus.ACCEPTED
+ ),
+ getAttendanceEntity(
+ of(2018, 9, 14),
+ SentExcuseStatus.WAITING
+ ),
+ getAttendanceEntity(
+ of(2018, 9, 17),
+ SentExcuseStatus.ACCEPTED
+ )
))
val attendance = attendanceLocal
@@ -50,4 +60,25 @@ class AttendanceLocalTest {
assertEquals(attendance[0].date, of(2018, 9, 10))
assertEquals(attendance[1].date, of(2018, 9, 14))
}
+
+ private fun getAttendanceEntity(
+ date: LocalDate,
+ excuseStatus: SentExcuseStatus
+ ) = Attendance(
+ studentId = 1,
+ diaryId = 2,
+ timeId = 3,
+ date = date,
+ number = 0,
+ subject = "",
+ name = "",
+ presence = false,
+ absence = false,
+ exemption = false,
+ lateness = false,
+ excused = false,
+ deleted = false,
+ excusable = false,
+ excuseStatus = excuseStatus.name
+ )
}
diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt
index 65ef1fcb..efad0d4d 100644
--- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt
+++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt
@@ -5,7 +5,6 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.LuckyNumber
-import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import org.junit.After
import org.junit.Before
diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt
index 6351997d..d03a319a 100644
--- a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt
+++ b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt
@@ -2,7 +2,6 @@
package io.github.wulkanowy.utils
-import android.content.Context
import timber.log.Timber
open class TimberTreeNoOp : Timber.Tree() {
diff --git a/app/src/main/assets/message-print-page.html b/app/src/main/assets/message-print-page.html
new file mode 100644
index 00000000..8da7dec6
--- /dev/null
+++ b/app/src/main/assets/message-print-page.html
@@ -0,0 +1,94 @@
+
+
+
+
+ %SUBJECT% | Wulkanowy
+
+
+
+%SUBJECT%
+
+
+ %INFO%
+
+
+
+
+
Treść wiadomości
+ %CONTENT%
+
+
+
diff --git a/app/src/main/assets/wulkanowy-logo-black.svg b/app/src/main/assets/wulkanowy-logo-black.svg
new file mode 100644
index 00000000..9bfbe2c0
--- /dev/null
+++ b/app/src/main/assets/wulkanowy-logo-black.svg
@@ -0,0 +1,74 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt
index 9540372f..19af1b2f 100644
--- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt
@@ -1,17 +1,15 @@
package io.github.wulkanowy.data
-import android.app.AlarmManager
import android.content.Context
import android.content.SharedPreferences
import android.content.res.AssetManager
import android.content.res.Resources
-import androidx.core.content.getSystemService
import androidx.preference.PreferenceManager
-import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
-import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager
+import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
+import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy
import dagger.Module
import dagger.Provides
import io.github.wulkanowy.data.db.AppDatabase
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
index 762c52f8..a31b68c0 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
@@ -68,6 +68,7 @@ import io.github.wulkanowy.data.db.migrations.Migration22
import io.github.wulkanowy.data.db.migrations.Migration23
import io.github.wulkanowy.data.db.migrations.Migration24
import io.github.wulkanowy.data.db.migrations.Migration25
+import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5
@@ -110,7 +111,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
- const val VERSION_SCHEMA = 25
+ const val VERSION_SCHEMA = 26
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array {
return arrayOf(
@@ -137,7 +138,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration22(),
Migration23(),
Migration24(),
- Migration25()
+ Migration25(),
+ Migration26()
)
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt
index 6e29112b..dd3126d4 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt
@@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
+import org.threeten.bp.LocalDateTime
@Entity(tableName = "GradesSummary")
data class GradeSummary(
@@ -36,4 +37,16 @@ data class GradeSummary(
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
+
+ @ColumnInfo(name = "is_predicted_grade_notified")
+ var isPredictedGradeNotified: Boolean = true
+
+ @ColumnInfo(name = "is_final_grade_notified")
+ var isFinalGradeNotified: Boolean = true
+
+ @ColumnInfo(name = "predicted_grade_last_change")
+ var predictedGradeLastChange: LocalDateTime = LocalDateTime.now()
+
+ @ColumnInfo(name = "final_grade_last_change")
+ var finalGradeLastChange: LocalDateTime = LocalDateTime.now()
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt
new file mode 100644
index 00000000..7130d86d
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt
@@ -0,0 +1,14 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+class Migration26 : Migration(25, 26) {
+
+ override fun migrate(database: SupportSQLiteDatabase) {
+ database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_predicted_grade_notified INTEGER NOT NULL DEFAULT 1")
+ database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_final_grade_notified INTEGER NOT NULL DEFAULT 1")
+ database.execSQL("ALTER TABLE GradesSummary ADD COLUMN predicted_grade_last_change INTEGER NOT NULL DEFAULT 0")
+ database.execSQL("ALTER TABLE GradesSummary ADD COLUMN final_grade_last_change INTEGER NOT NULL DEFAULT 0")
+ }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt
index 6a0b2d32..76cf698c 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt
@@ -10,7 +10,7 @@ import javax.inject.Singleton
@Singleton
class AppCreatorRepository @Inject constructor(private val assets: AssetManager) {
fun getAppCreators(): Single> {
- return Single.fromCallable> {
+ return Single.fromCallable {
Gson().fromJson(
assets.open("contributors.json").bufferedReader().use { it.readText() },
Array::class.java
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt
index 0f484d32..389eb583 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt
@@ -13,7 +13,7 @@ class ExamLocal @Inject constructor(private val examDb: ExamDao) {
fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> {
return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate)
- .filter { !it.isEmpty() }
+ .filter { it.isNotEmpty() }
}
fun saveExams(exams: List) {
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt
index 944ed34a..52ab6017 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt
@@ -27,6 +27,10 @@ class GradeLocal @Inject constructor(
gradeDb.updateAll(grades)
}
+ fun updateGradesSummary(gradesSummary: List) {
+ gradeSummaryDb.updateAll(gradesSummary)
+ }
+
fun getGradesDetails(semester: Semester): Maybe> {
return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() }
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt
index e28350a5..2ba68b96 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Completable
import io.reactivex.Single
+import org.threeten.bp.LocalDateTime
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@@ -43,7 +44,31 @@ class GradeRepository @Inject constructor(
local.getGradesSummary(semester).toSingle(emptyList())
.doOnSuccess { old ->
local.deleteGradesSummary(old.uniqueSubtract(newSummary))
- local.saveGradesSummary(newSummary.uniqueSubtract(old))
+ local.saveGradesSummary(newSummary.uniqueSubtract(old)
+ .onEach { summary ->
+ val oldSummary = old.find { oldSummary -> oldSummary.subject == summary.subject }
+ summary.isPredictedGradeNotified = when {
+ summary.predictedGrade.isEmpty() -> true
+ notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
+ else -> true
+ }
+ summary.isFinalGradeNotified = when {
+ summary.finalGrade.isEmpty() -> true
+ notify && oldSummary?.finalGrade != summary.finalGrade -> false
+ else -> true
+ }
+
+ summary.predictedGradeLastChange = when {
+ oldSummary == null -> LocalDateTime.now()
+ summary.predictedGrade != oldSummary.predictedGrade -> LocalDateTime.now()
+ else -> oldSummary.predictedGradeLastChange
+ }
+ summary.finalGradeLastChange = when {
+ oldSummary == null -> LocalDateTime.now()
+ summary.finalGrade != oldSummary.finalGrade -> LocalDateTime.now()
+ else -> oldSummary.finalGradeLastChange
+ }
+ })
}
}
}.flatMap {
@@ -63,6 +88,14 @@ class GradeRepository @Inject constructor(
return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList())
}
+ fun getNotNotifiedPredictedGrades(semester: Semester): Single> {
+ return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } }.toSingle(emptyList())
+ }
+
+ fun getNotNotifiedFinalGrades(semester: Semester): Single> {
+ return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } }.toSingle(emptyList())
+ }
+
fun updateGrade(grade: Grade): Completable {
return Completable.fromCallable { local.updateGrades(listOf(grade)) }
}
@@ -70,4 +103,8 @@ class GradeRepository @Inject constructor(
fun updateGrades(grades: List): Completable {
return Completable.fromCallable { local.updateGrades(grades) }
}
+
+ fun updateGradesSummary(gradesSummary: List): Completable {
+ return Completable.fromCallable { local.updateGradesSummary(gradesSummary) }
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt
index 9b1d4ac2..ff817544 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt
@@ -12,7 +12,7 @@ import javax.inject.Singleton
class RecipientLocal @Inject constructor(private val recipientDb: RecipientDao) {
fun getRecipients(student: Student, role: Int, unit: ReportingUnit): Maybe> {
- return recipientDb.load(student.studentId, role, unit.realId).filter { !it.isEmpty() }
+ return recipientDb.load(student.studentId, role, unit.realId).filter { it.isNotEmpty() }
}
fun saveRecipients(recipients: List): List {
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt
index 6f9eec3f..0631c668 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt
@@ -11,7 +11,7 @@ import javax.inject.Singleton
class ReportingUnitLocal @Inject constructor(private val reportingUnitDb: ReportingUnitDao) {
fun getReportingUnits(student: Student): Maybe> {
- return reportingUnitDb.load(student.studentId).filter { !it.isEmpty() }
+ return reportingUnitDb.load(student.studentId).filter { it.isNotEmpty() }
}
fun getReportingUnit(student: Student, unitId: Int): Maybe {
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
index 15c33650..4c0ffd82 100644
--- 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
@@ -14,7 +14,7 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) {
private fun mapStudents(students: List, email: String, password: String): List {
return students.map { student ->
Student(
- email = email,
+ email = email.ifBlank { student.email },
password = password,
isParent = student.isParent,
symbol = student.symbol,
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt
index 327c7174..c4681fb8 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt
@@ -9,7 +9,7 @@ import javax.inject.Inject
class GradeStatisticsWork @Inject constructor(private val gradeStatisticsRepository: GradeStatisticsRepository) : Work {
override fun create(student: Student, semester: Semester): Completable {
- return gradeStatisticsRepository.getGradesStatistics(student, semester, "Wszystkie", false, true)
+ return gradeStatisticsRepository.getGradesStatistics(student, semester, "Wszystkie", false, forceRefresh = true)
.ignoreElement()
}
}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt
index 6e90826a..fcdaad6e 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt
@@ -9,6 +9,7 @@ import androidx.core.app.NotificationCompat.PRIORITY_HIGH
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository
@@ -30,17 +31,21 @@ class GradeWork @Inject constructor(
override fun create(student: Student, semester: Semester): Completable {
return gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable)
- .flatMap { gradeRepository.getNotNotifiedGrades(semester) }
- .flatMapCompletable {
- if (it.isNotEmpty()) notify(it)
+ .ignoreElement()
+ .concatWith(Completable.concatArray(gradeRepository.getNotNotifiedGrades(semester).flatMapCompletable {
+ if (it.isNotEmpty()) notifyDetails(it)
gradeRepository.updateGrades(it.onEach { grade -> grade.isNotified = true })
- }
+ }, gradeRepository.getNotNotifiedPredictedGrades(semester).flatMapCompletable {
+ if (it.isNotEmpty()) notifyPredicted(it)
+ gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isPredictedGradeNotified = true })
+ }, gradeRepository.getNotNotifiedFinalGrades(semester).flatMapCompletable {
+ if (it.isNotEmpty()) notifyFinal(it)
+ gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isFinalGradeNotified = true })
+ }))
}
- private fun notify(grades: List) {
- notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID)
- .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size))
- .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size))
+ private fun getNotificationBuilder(): NotificationCompat.Builder {
+ return NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_grade)
.setAutoCancel(true)
.setPriority(PRIORITY_HIGH)
@@ -49,6 +54,12 @@ class GradeWork @Inject constructor(
.setContentIntent(
PendingIntent.getActivity(context, MainView.Section.GRADE.id,
MainActivity.getStartIntent(context, MainView.Section.GRADE, true), FLAG_UPDATE_CURRENT))
+ }
+
+ private fun notifyDetails(grades: List) {
+ notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder()
+ .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size))
+ .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size))
.setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size))
grades.forEach { addLine("${it.subject}: ${it.entry}") }
@@ -57,4 +68,30 @@ class GradeWork @Inject constructor(
.build()
)
}
+
+ private fun notifyPredicted(gradesSummary: List) {
+ notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder()
+ .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items_predicted, gradesSummary.size, gradesSummary.size))
+ .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items_predicted, gradesSummary.size, gradesSummary.size))
+ .setStyle(NotificationCompat.InboxStyle().run {
+ setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, gradesSummary.size, gradesSummary.size))
+ gradesSummary.forEach { addLine("${it.subject}: ${it.predictedGrade}") }
+ this
+ })
+ .build()
+ )
+ }
+
+ private fun notifyFinal(gradesSummary: List) {
+ notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder()
+ .setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items_final, gradesSummary.size, gradesSummary.size))
+ .setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items_final, gradesSummary.size, gradesSummary.size))
+ .setStyle(NotificationCompat.InboxStyle().run {
+ setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, gradesSummary.size, gradesSummary.size))
+ gradesSummary.forEach { addLine("${it.subject}: ${it.finalGrade}") }
+ this
+ })
+ .build()
+ )
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt
index 4485cb3e..33eb1122 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt
@@ -25,9 +25,9 @@ class LogViewerPresenter @Inject constructor(
disposable.add(loggerRepository.getLogFiles()
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
- .subscribe({
- Timber.i("Loading logs files result: ${it.joinToString { it.name }}")
- view?.shareLogs(it)
+ .subscribe({ files ->
+ Timber.i("Loading logs files result: ${files.joinToString { it.name }}")
+ view?.shareLogs(files)
}, {
Timber.i("Loading logs files result: An exception occurred")
errorHandler.dispatch(it)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt
new file mode 100644
index 00000000..dbcb499e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt
@@ -0,0 +1,3 @@
+package io.github.wulkanowy.ui.modules.account
+
+data class Account(val email: String, val isParent: Boolean)
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 7df0ca37..27915a71 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
@@ -3,33 +3,72 @@ package io.github.wulkanowy.ui.modules.account
import android.annotation.SuppressLint
import android.graphics.PorterDuff
import android.view.LayoutInflater
+import android.view.View.GONE
+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.databinding.HeaderAccountBinding
import io.github.wulkanowy.databinding.ItemAccountBinding
+import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
-class AccountAdapter @Inject constructor() : RecyclerView.Adapter() {
+class AccountAdapter @Inject constructor() : RecyclerView.Adapter() {
- var items = emptyList()
+ var items = emptyList>()
var onClickListener: (Student) -> Unit = {}
override fun getItemCount() = items.size
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
- ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- )
+ override fun getItemViewType(position: Int) = items[position].viewType.id
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+
+ return when (viewType) {
+ 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 Student)
+ }
+ }
+
+ private fun bindHeaderViewHolder(binding: HeaderAccountBinding, account: Account) {
+ with(binding) {
+ accountHeaderEmail.text = account.email
+ accountHeaderType.setText(if (account.isParent) R.string.account_type_parent else R.string.account_type_student)
+ }
+ }
@SuppressLint("SetTextI18n")
- override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
- val student = items[position]
-
- with(holder.binding) {
+ private fun bindItemViewHolder(binding: ItemAccountBinding, student: Student) {
+ with(binding) {
accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = 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
+ }
+ }
+ }
with(accountItemImage) {
val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
@@ -42,5 +81,8 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter(), AccountView {
}
}
- override fun updateData(data: List) {
+ override fun updateData(data: List>) {
with(accountAdapter) {
items = data
notifyDataSetChanged()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt
new file mode 100644
index 00000000..05a4a69c
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt
@@ -0,0 +1,9 @@
+package io.github.wulkanowy.ui.modules.account
+
+data class AccountItem(val value: T, val viewType: ViewType) {
+
+ enum class ViewType(val id: Int) {
+ HEADER(1),
+ ITEM(2)
+ }
+}
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 3416a043..b5fbcdb6 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
@@ -83,11 +83,20 @@ class AccountPresenter @Inject constructor(
}
}
+ private fun createAccountItems(items: List): List> {
+ return items.groupBy { Account(it.email, it.isParent) }.map { (account, students) ->
+ listOf(AccountItem(account, AccountItem.ViewType.HEADER)) + students.map { student ->
+ AccountItem(student, AccountItem.ViewType.ITEM)
+ }
+ }.flatten()
+ }
+
private fun loadData() {
Timber.i("Loading account data started")
disposable.add(studentRepository.getSavedStudents(false)
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
+ .map { createAccountItems(it) }
.subscribe({
Timber.i("Loading account result: Success")
view?.updateData(it)
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 abb9e1d2..a1f8086c 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,13 +1,12 @@
package io.github.wulkanowy.ui.modules.account
-import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView
interface AccountView : BaseView {
fun initView()
- fun updateData(data: List)
+ fun updateData(data: List>)
fun dismissView()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
index 437e06c9..f58d0617 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
@@ -20,7 +20,6 @@ import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber
-import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
class AttendancePresenter @Inject constructor(
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 bda098f4..af169932 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
@@ -98,7 +98,7 @@ class GradeAverageProvider @Inject constructor(
}
private fun List.emulateEmptySummaries(student: Student, semester: Semester, grades: List>>, calcAverage: Boolean): List {
- if (isNotEmpty() && size == grades.size) return this
+ if (isNotEmpty() && size > grades.size) return this
return grades.mapIndexed { i, (subject, details) ->
singleOrNull { it.subject == subject }?.let { return@mapIndexed it }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt
index 436dee53..a94d2cfc 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt
@@ -63,7 +63,7 @@ class MessagePreviewAdapter @Inject constructor() :
@SuppressLint("SetTextI18n")
private fun bindMessage(holder: MessageViewHolder, message: Message) {
with(holder.binding) {
- messagePreviewSubject.text = if (message.subject.isNotBlank()) message.subject else root.context.getString(R.string.message_no_subject)
+ messagePreviewSubject.text = message.subject.ifBlank { root.context.getString(R.string.message_no_subject) }
messagePreviewDate.text = root.context.getString(R.string.message_date, message.date.toFormattedString("yyyy-MM-dd HH:mm:ss"))
messagePreviewContent.text = message.content
messagePreviewAuthor.text = if (message.folderId == MessageFolder.SENT.id) "${root.context.getString(R.string.message_to)} ${message.recipient}"
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
index 99eede15..575db75b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
@@ -1,12 +1,20 @@
package io.github.wulkanowy.ui.modules.message.preview
+import android.os.Build
import android.os.Bundle
+import android.print.PrintAttributes
+import android.print.PrintManager
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.annotation.RequiresApi
+import androidx.core.content.getSystemService
import androidx.recyclerview.widget.LinearLayoutManager
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message
@@ -17,6 +25,8 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
+import io.github.wulkanowy.utils.AppInfo
+import io.github.wulkanowy.utils.shareText
import javax.inject.Inject
class MessagePreviewFragment :
@@ -29,18 +39,31 @@ class MessagePreviewFragment :
@Inject
lateinit var previewAdapter: MessagePreviewAdapter
+ @Inject
+ lateinit var appInfo: AppInfo
+
private var menuReplyButton: MenuItem? = null
private var menuForwardButton: MenuItem? = null
private var menuDeleteButton: MenuItem? = null
+ private var menuShareButton: MenuItem? = null
+
+ private var menuPrintButton: MenuItem? = null
+
override val titleStringId: Int
get() = R.string.message_title
override val deleteMessageSuccessString: String
get() = getString(R.string.message_delete_success)
+ override val messageNoSubjectString: String
+ get() = getString(R.string.message_no_subject)
+
+ override val printHTML: String
+ get() = requireContext().assets.open("message-print-page.html").bufferedReader().use { it.readText() }
+
companion object {
const val MESSAGE_ID_KEY = "message_id"
@@ -77,6 +100,8 @@ class MessagePreviewFragment :
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
+ menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
+ menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
presenter.onCreateOptionsMenu()
}
@@ -85,6 +110,8 @@ class MessagePreviewFragment :
R.id.messagePreviewMenuReply -> presenter.onReply()
R.id.messagePreviewMenuForward -> presenter.onForward()
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
+ R.id.messagePreviewMenuShare -> presenter.onShare()
+ R.id.messagePreviewMenuPrint -> presenter.onPrint()
else -> false
}
}
@@ -108,6 +135,8 @@ class MessagePreviewFragment :
menuReplyButton?.isVisible = show
menuForwardButton?.isVisible = show
menuDeleteButton?.isVisible = show
+ menuShareButton?.isVisible = show
+ menuPrintButton?.isVisible = show && appInfo.systemVersion >= Build.VERSION_CODES.LOLLIPOP
}
override fun setDeletedOptionsLabels() {
@@ -138,6 +167,38 @@ class MessagePreviewFragment :
context?.let { it.startActivity(SendMessageActivity.getStartIntent(it, message)) }
}
+ override fun shareText(text: String, subject: String) {
+ context?.shareText(text, subject)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ override fun printDocument(html: String, jobName: String) {
+ val webView = WebView(activity)
+ webView.webViewClient = object : WebViewClient() {
+
+ override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) = false
+
+ override fun onPageFinished(view: WebView, url: String) {
+ createWebPrintJob(view, jobName)
+ }
+ }
+
+ webView.loadDataWithBaseURL("file:///android_asset/", html, "text/HTML", "UTF-8", null)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ private fun createWebPrintJob(webView: WebView, jobName: String) {
+ activity?.getSystemService()?.let { printManager ->
+ val printAdapter = webView.createPrintDocumentAdapter(jobName)
+
+ printManager.print(
+ jobName,
+ printAdapter,
+ PrintAttributes.Builder().build()
+ )
+ }
+ }
+
override fun popView() {
(activity as MainActivity).popView()
}
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 24678c70..db7996bc 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
@@ -1,12 +1,17 @@
package io.github.wulkanowy.ui.modules.message.preview
+import android.annotation.SuppressLint
+import android.os.Build
import io.github.wulkanowy.data.db.entities.Message
+import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.repositories.message.MessageRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
+import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
+import io.github.wulkanowy.utils.toFormattedString
import timber.log.Timber
import javax.inject.Inject
@@ -15,11 +20,14 @@ class MessagePreviewPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val messageRepository: MessageRepository,
- private val analytics: FirebaseAnalyticsHelper
+ private val analytics: FirebaseAnalyticsHelper,
+ private var appInfo: AppInfo
) : BasePresenter(errorHandler, studentRepository, schedulers) {
var message: Message? = null
+ var attachments: List? = null
+
private lateinit var lastError: Throwable
private var retryCallback: () -> Unit = {}
@@ -56,6 +64,7 @@ class MessagePreviewPresenter @Inject constructor(
.subscribe({ message ->
Timber.i("Loading message ${message.message.messageId} preview result: Success ")
this@MessagePreviewPresenter.message = message.message
+ this@MessagePreviewPresenter.attachments = message.attachments
view?.apply {
setMessageWithAttachment(message)
initOptions()
@@ -87,6 +96,60 @@ class MessagePreviewPresenter @Inject constructor(
} else false
}
+ fun onShare(): Boolean {
+ message?.let {
+ var text = "Temat: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}\n" + when (it.sender.isNotEmpty()) {
+ true -> "Od: ${it.sender}\n"
+ false -> "Do: ${it.recipient}\n"
+ } + "Data: ${it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${it.content}"
+
+ attachments?.let { attachments ->
+ if (attachments.isNotEmpty()) {
+ text += "\n\nZałączniki:"
+
+ attachments.forEach { attachment ->
+ text += "\n${attachment.filename}: ${attachment.url}"
+ }
+ }
+ }
+
+ view?.shareText(text, "FW: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}")
+ return true
+ }
+ return false
+ }
+
+ @SuppressLint("NewApi")
+ fun onPrint(): Boolean {
+ if (appInfo.systemVersion < Build.VERSION_CODES.LOLLIPOP) return false
+ message?.let {
+ val dateString = it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
+ val infoContent = "Data wysłania
$dateString" + when {
+ it.sender.isNotEmpty() -> "Od
${it.sender}"
+ else -> "Do
${it.recipient}"
+ }
+
+ val messageContent = "${it.content}
"
+ .replace(Regex("[\\n\\r]{2,}"), "")
+ .replace(Regex("[\\n\\r]"), "
")
+
+ val jobName = "Wiadomość " + when {
+ it.sender.isNotEmpty() -> "od ${it.sender}"
+ else -> "do ${it.recipient}"
+ } + " $dateString: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }} | Wulkanowy"
+
+ view?.apply {
+ val html = printHTML
+ .replace("%SUBJECT%", it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() })
+ .replace("%CONTENT%", messageContent)
+ .replace("%INFO%", infoContent)
+ printDocument(html, jobName)
+ }
+ return true
+ }
+ return false
+ }
+
private fun deleteMessage() {
message?.let { message ->
disposable.add(studentRepository.getCurrentStudent()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
index 3d620459..0fdb4bda 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
@@ -1,5 +1,7 @@
package io.github.wulkanowy.ui.modules.message.preview
+import android.os.Build
+import androidx.annotation.RequiresApi
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.ui.base.BaseView
@@ -8,6 +10,10 @@ interface MessagePreviewView : BaseView {
val deleteMessageSuccessString: String
+ val messageNoSubjectString: String
+
+ val printHTML: String
+
fun initView()
fun setMessageWithAttachment(item: MessageWithAttachment)
@@ -34,5 +40,10 @@ interface MessagePreviewView : BaseView {
fun openMessageForward(message: Message?)
+ fun shareText(text: String, subject: String)
+
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ fun printDocument(html: String, jobName: String)
+
fun popView()
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt
index ece6773f..b58508a9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt
@@ -4,10 +4,9 @@ import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
-import androidx.recyclerview.widget.SortedList
-import androidx.recyclerview.widget.SortedListAdapterCallback
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.repositories.message.MessageFolder
@@ -20,39 +19,23 @@ class MessageTabAdapter @Inject constructor() :
var onClickListener: (Message, position: Int) -> Unit = { _, _ -> }
- private val items = SortedList(Message::class.java, object :
- SortedListAdapterCallback(this) {
+ private var items = mutableListOf()
- override fun compare(item1: Message, item2: Message): Int {
- return item2.date.compareTo(item1.date)
- }
-
- override fun areContentsTheSame(oldItem: Message?, newItem: Message?): Boolean {
- return oldItem == newItem
- }
-
- override fun areItemsTheSame(item1: Message, item2: Message): Boolean {
- return item1 == item2
- }
- })
-
- fun replaceAll(models: List) {
- items.beginBatchedUpdates()
- for (i in items.size() - 1 downTo 0) {
- val model = items.get(i)
- if (model !in models) {
- items.remove(model)
- }
- }
- items.addAll(models)
- items.endBatchedUpdates()
+ fun setDataItems(data: List) {
+ val diffResult = DiffUtil.calculateDiff(MessageTabDiffUtil(items, data))
+ items = data.toMutableList()
+ diffResult.dispatchUpdatesTo(this)
}
fun updateItem(position: Int, item: Message) {
- items.updateItemAt(position, item)
+ val currentItem = items[position]
+ items[position] = item
+ if (item != currentItem) {
+ notifyItemChanged(position)
+ }
}
- override fun getItemCount() = items.size()
+ override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@@ -85,4 +68,19 @@ class MessageTabAdapter @Inject constructor() :
}
class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root)
+
+ private class MessageTabDiffUtil(private val old: List, private val new: List) :
+ DiffUtil.Callback() {
+ override fun getOldListSize(): Int = old.size
+
+ override fun getNewListSize(): Int = new.size
+
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return old[oldItemPosition].id == new[newItemPosition].id
+ }
+
+ override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return old[oldItemPosition] == new[newItemPosition]
+ }
+ }
}
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 909bb687..9954c642 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
@@ -90,7 +90,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag
}
override fun updateData(data: List) {
- tabAdapter.replaceAll(data)
+ tabAdapter.setDataItems(data)
}
override fun updateItem(item: Message, position: Int) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
index 221762d1..533f5ac8 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
@@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.message.tab
-import android.annotation.SuppressLint
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.repositories.message.MessageFolder
import io.github.wulkanowy.data.repositories.message.MessageRepository
@@ -11,8 +10,13 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.toFormattedString
+import io.reactivex.subjects.PublishSubject
+import me.xdrop.fuzzywuzzy.FuzzySearch
import timber.log.Timber
+import java.util.Locale
+import java.util.concurrent.TimeUnit
import javax.inject.Inject
+import kotlin.math.pow
class MessageTabPresenter @Inject constructor(
schedulers: SchedulersProvider,
@@ -31,9 +35,12 @@ class MessageTabPresenter @Inject constructor(
private var messages = emptyList()
+ private val searchQuery = PublishSubject.create()
+
fun onAttachView(view: MessageTabView, folder: MessageFolder) {
super.onAttachView(view)
view.initView()
+ initializeSearchStream()
errorHandler.showErrorMessage = ::showErrorViewOnError
this.folder = folder
}
@@ -76,38 +83,35 @@ class MessageTabPresenter @Inject constructor(
private fun loadData(forceRefresh: Boolean) {
Timber.i("Loading $folder message data started")
- disposable.apply {
- clear()
- add(studentRepository.getCurrentStudent()
- .flatMap { student ->
- semesterRepository.getCurrentSemester(student)
- .flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) }
+ disposable.add(studentRepository.getCurrentStudent()
+ .flatMap { student ->
+ semesterRepository.getCurrentSemester(student)
+ .flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) }
+ }
+ .subscribeOn(schedulers.backgroundThread)
+ .observeOn(schedulers.mainThread)
+ .doFinally {
+ view?.run {
+ showRefresh(false)
+ showProgress(false)
+ enableSwipe(true)
+ notifyParentDataLoaded()
}
- .subscribeOn(schedulers.backgroundThread)
- .observeOn(schedulers.mainThread)
- .doFinally {
- view?.run {
- showRefresh(false)
- showProgress(false)
- enableSwipe(true)
- notifyParentDataLoaded()
- }
- }
- .subscribe({
- Timber.i("Loading $folder message result: Success")
- messages = it
- onSearchQueryTextChange(lastSearchQuery)
- analytics.logEvent(
- "load_data",
- "type" to "messages",
- "items" to it.size,
- "folder" to folder.name
- )
- }) {
- Timber.i("Loading $folder message result: An exception occurred")
- errorHandler.dispatch(it)
- })
- }
+ }
+ .subscribe({
+ Timber.i("Loading $folder message result: Success")
+ messages = it
+ view?.updateData(getFilteredData(lastSearchQuery))
+ analytics.logEvent(
+ "load_data",
+ "type" to "messages",
+ "items" to it.size,
+ "folder" to folder.name
+ )
+ }) {
+ Timber.i("Loading $folder message result: An exception occurred")
+ errorHandler.dispatch(it)
+ })
}
private fun showErrorViewOnError(message: String, error: Throwable) {
@@ -121,25 +125,36 @@ class MessageTabPresenter @Inject constructor(
}
}
- @SuppressLint("DefaultLocale")
fun onSearchQueryTextChange(query: String) {
- lastSearchQuery = query
+ if (query != searchQuery.toString())
+ searchQuery.onNext(query)
+ }
- val lowerCaseQuery = query.toLowerCase()
- val filteredList = mutableListOf()
- messages.forEach {
- if (lowerCaseQuery in it.subject.toLowerCase() ||
- lowerCaseQuery in it.sender.toLowerCase() ||
- lowerCaseQuery in it.recipient.toLowerCase() ||
- lowerCaseQuery in it.date.toFormattedString()
- ) {
- filteredList.add(it)
+ private fun initializeSearchStream() {
+ disposable.add(searchQuery
+ .debounce(250, TimeUnit.MILLISECONDS)
+ .map { query ->
+ lastSearchQuery = query
+ getFilteredData(query)
}
+ .subscribeOn(schedulers.backgroundThread)
+ .observeOn(schedulers.mainThread)
+ .subscribe({
+ Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${it.size}")
+ updateData(it)
+ }) { Timber.e(it) })
+ }
+
+ private fun getFilteredData(query: String): List {
+ return if (query.trim().isEmpty()) {
+ messages.sortedByDescending { it.date }
+ } else {
+ messages
+ .map { it to calculateMatchRatio(it, query) }
+ .sortedByDescending { it.second }
+ .filter { it.second > 5000 }
+ .map { it.first }
}
-
- Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${filteredList.size}")
-
- updateData(filteredList)
}
private fun updateData(data: List) {
@@ -151,4 +166,42 @@ class MessageTabPresenter @Inject constructor(
resetListPosition()
}
}
+
+ private fun calculateMatchRatio(message: Message, query: String): Int {
+ val subjectRatio = FuzzySearch.tokenSortPartialRatio(
+ query.toLowerCase(Locale.getDefault()),
+ message.subject
+ )
+
+ val senderOrRecipientRatio = FuzzySearch.tokenSortPartialRatio(
+ query.toLowerCase(Locale.getDefault()),
+ if (message.sender.isNotEmpty()) message.sender.toLowerCase(Locale.getDefault())
+ else message.recipient.toLowerCase(Locale.getDefault())
+ )
+
+ val dateRatio = listOf(
+ FuzzySearch.ratio(
+ query.toLowerCase(Locale.getDefault()),
+ message.date.toFormattedString("dd.MM").toLowerCase(Locale.getDefault())
+ ),
+ FuzzySearch.ratio(
+ query.toLowerCase(Locale.getDefault()),
+ message.date.toFormattedString("dd.MM.yyyy").toLowerCase(Locale.getDefault())
+ ),
+ FuzzySearch.ratio(
+ query.toLowerCase(Locale.getDefault()),
+ message.date.toFormattedString("d MMMM").toLowerCase(Locale.getDefault())
+ ),
+ FuzzySearch.ratio(
+ query.toLowerCase(Locale.getDefault()),
+ message.date.toFormattedString("d MMMM yyyy").toLowerCase(Locale.getDefault())
+ )
+ ).max() ?: 0
+
+
+ return (subjectRatio.toDouble().pow(2)
+ + senderOrRecipientRatio.toDouble().pow(2)
+ + dateRatio.toDouble().pow(2) * 2
+ ).toInt()
+ }
}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt
index 92aeb581..f72d753a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt
@@ -43,7 +43,7 @@ class CompletedLessonsPresenter @Inject constructor(
completedLessonsErrorHandler.showErrorMessage = ::showErrorViewOnError
completedLessonsErrorHandler.onFeatureDisabled = {
this.view?.showFeatureDisabled()
- this.view?.showEmpty(true);
+ this.view?.showEmpty(true)
Timber.i("Completed lessons feature disabled by school")
}
loadData(ofEpochDay(date ?: baseDate.toEpochDay()))
diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt
index 2b40cb47..cf715e65 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt
@@ -71,4 +71,17 @@ fun Context.openDialer(phone: String) {
startActivity(intent)
}
+fun Context.shareText(text: String, subject: String?) {
+ val sendIntent: Intent = Intent().apply {
+ action = Intent.ACTION_SEND
+ putExtra(Intent.EXTRA_TEXT, text)
+ if (subject != null) {
+ putExtra(Intent.EXTRA_SUBJECT, subject)
+ }
+ type = "text/plain"
+ }
+ val shareIntent = Intent.createChooser(sendIntent, null)
+ startActivity(shareIntent)
+}
+
fun Context.dpToPx(dp: Float) = dp * resources.displayMetrics.densityDpi / DENSITY_DEFAULT
diff --git a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt
index 2c49ee97..b96faeb2 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt
@@ -52,4 +52,4 @@ class LifecycleAwareVariableActivity : ReadWriteProperty Fragment.lifecycleAwareVariable() = LifecycleAwareVariable()
-fun AppCompatActivity.lifecycleAwareVariable() = LifecycleAwareVariableActivity()
+fun lifecycleAwareVariable() = LifecycleAwareVariableActivity()
diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
index e4d4163b..63a30db8 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt
@@ -23,6 +23,8 @@ fun Sdk.init(student: Student): Sdk {
certKey = student.certificateKey
privateKey = student.privateKey
+ emptyCookieJarInterceptor = true
+
Timber.d("Sdk in ${student.loginMode} mode reinitialized")
return this
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 db9e9a9a..68045da4 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,6 +1,8 @@
-Wersja 0.18.3
-- poprawiliśmy liczenie średniej i dodaliśmy nowy sposób jej liczenia w ustawieniach
-- naprawiliśmy usuwanie wiadomości
-- naprawiliśmy wysyłanie wiadomości na Lubelskim Portalu Oświatowym
+Wersja 0.19.0
+- naprawiliśmy pokazywanie brakujących przedmiotów na liście podsumowania ocen
+- ulepszyliśmy wygląd menadżera kont
+- ulepszyliśmy wyszukiwarkę wiadomości
+- dodaliśmy powiadomienia o proponowanych i końcowych ocenach
+- dodaliśmy opcję udostępniania i drukowania wiadomości
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
diff --git a/app/src/main/res/drawable/ic_menu_message_print.xml b/app/src/main/res/drawable/ic_menu_message_print.xml
new file mode 100644
index 00000000..204b0f6e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_message_print.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_menu_message_share.xml b/app/src/main/res/drawable/ic_menu_message_share.xml
new file mode 100644
index 00000000..67a8ee49
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_message_share.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/dialog_account.xml b/app/src/main/res/layout/dialog_account.xml
index 6e975c80..d56de3a2 100644
--- a/app/src/main/res/layout/dialog_account.xml
+++ b/app/src/main/res/layout/dialog_account.xml
@@ -5,45 +5,60 @@
android:layout_width="300dp"
android:layout_height="wrap_content">
-
-
-
+ android:orientation="vertical">
-
+
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/header_account.xml b/app/src/main/res/layout/header_account.xml
new file mode 100644
index 00000000..6219c26d
--- /dev/null
+++ b/app/src/main/res/layout/header_account.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml
index 614e4d2d..9568b345 100644
--- a/app/src/main/res/layout/item_account.xml
+++ b/app/src/main/res/layout/item_account.xml
@@ -3,20 +3,17 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="56dp"
+ android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:orientation="horizontal"
- android:paddingStart="24dp"
- android:paddingLeft="24dp"
- android:paddingEnd="24dp"
- android:paddingRight="24dp"
+ android:paddingVertical="8dp"
+ android:paddingHorizontal="16dp"
tools:context=".ui.modules.account.AccountAdapter">
+
+
diff --git a/app/src/main/res/menu/action_menu_message_preview.xml b/app/src/main/res/menu/action_menu_message_preview.xml
index dfc12e23..4c1332e1 100644
--- a/app/src/main/res/menu/action_menu_message_preview.xml
+++ b/app/src/main/res/menu/action_menu_message_preview.xml
@@ -22,4 +22,18 @@
android:title="@string/message_delete"
app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" />
+
+
diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml
index 49628798..11935b49 100644
--- a/app/src/main/res/values-de/preferences_values.xml
+++ b/app/src/main/res/values-de/preferences_values.xml
@@ -36,8 +36,8 @@
- Durchschnittsnote für das 2. Semester
- - Average of grades from both semesters
- - Durchschnitt der Bewertungen für das ganze Jahr
+ - Durchschnitt der Noten aus beiden Semestern
+ - Durchschnitt der Noten aus dem ganzen Jahr
- Nicht zeigen
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 4b166937..7cc44fd9 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -103,10 +103,26 @@
- Neue Note
- Neue Noten
+
+ - New predicted grade
+ - New predicted grades
+
+
+ - New final grade
+ - New final grades
+
- Du hast %1$d Note bekommen
- Du hast %1$d Noten bekommen
+
+ - You received %1$d predicted grade
+ - You received %1$d predicted grades
+
+
+ - You received %1$d final grade
+ - You received %1$d final grades
+
Lektion
Klassenzimmer
@@ -172,6 +188,8 @@
In den Korb wandern
Dauerhaft löschen
Nachricht erfolgreich gelöscht
+ Share
+ Print
Thema
Inhalt
Nachricht erfolgreich gesendet
@@ -214,7 +232,7 @@
Die heutige Glücksnummer ist
Keine Information über die Glücksnummer.
Glücksnummer für heute
- Die heutige Glücksnummer ist:
+ Die heutige Glücksnummer ist: %d
Mobile Geräte
Keine Geräte
@@ -245,6 +263,10 @@
Abmelden
Wollen Sie sich von einem aktiven Studenten abmelden?
Abmeldung von Student
+ Student account
+ Parent account
+ Mobile API mode
+ Hybrid mode
Version der App
Mitarbeiter
@@ -270,8 +292,8 @@
Logs teilen
Aktualisieren
- Check for updates
- Before reporting a bug, check first if an update with the bug fix is available
+ Auf Updates prüfen
+ Bevor Sie einen Fehler melden, prüfen Sie zuerst, ob ein Update mit der Fehlerbehebung verfügbar ist
Inhalt
Wiederhol
@@ -289,7 +311,7 @@
Zurück
Nächste
Suchen
- Suchen...
+ Suchen…
Keine Lektionen
Thema wählen
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 7f25d8a6..bd4545e9 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -107,12 +107,36 @@
- Nowe oceny
- Nowe oceny
+
+ - Nowa ocena przewidywana
+ - Nowe oceny przewidywane
+ - Nowe oceny przewidywane
+ - Nowe oceny przewidywane
+
+
+ - Nowa ocena końcowa
+ - Nowe oceny końcowe
+ - Nowe oceny końcowe
+ - Nowe oceny końcowe
+
- Masz %1$d nową ocenę
- Masz %1$d nowe oceny
- Masz %1$d nowych ocen
- Masz %1$d nowych ocen
+
+ - Masz %1$d nową przewidywaną ocenę
+ - Masz %1$d nowe przewidywane oceny
+ - Masz %1$d nowych przewidywanych ocen
+ - Masz %1$d nowych przewidywanych ocen
+
+
+ - Masz %1$d nową końcową ocenę
+ - Masz %1$d nowe końcowe oceny
+ - Masz %1$d nowych końcowych ocen
+ - Masz %1$d nowych końcowych ocen
+
Lekcja
Sala
@@ -180,6 +204,8 @@
Przenieś do kosza
Usuń trwale
Wiadomość usunięta pomyślnie
+ Udostępnij
+ Drukuj
Temat
Treść
Wiadomość wysłana pomyślnie
@@ -265,6 +291,10 @@
Wyloguj
Czy chcesz wylogować aktualnego ucznia?
Wylogowanie ucznia
+ Konto ucznia
+ Konto rodzica
+ Tryb API mobilne
+ Tryb hybrydowy
Wersja aplikacji
Twórcy
@@ -309,7 +339,7 @@
Poprzedni
Następny
Szukaj
- Szukaj...
+ Szukaj…
Brak lekcji
Wybierz motyw
diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml
index bd8bb844..a41abf35 100644
--- a/app/src/main/res/values-ru/preferences_values.xml
+++ b/app/src/main/res/values-ru/preferences_values.xml
@@ -37,7 +37,7 @@
- Средняя оценка со 2 семестра
- Average of grades from both semesters
- - Средняя оценка с целого года
+ - Average of grades from the whole year
- Не показывать
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 6eb8f774..15c58317 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -107,12 +107,36 @@
- Новые оценки
- Новые оценки
+
+ - New predicted grade
+ - New predicted grades
+ - New predicted grades
+ - New predicted grades
+
+
+ - New final grade
+ - New final grades
+ - New final grades
+ - New final grades
+
- Вы получили %1$d оценку
- Вы получили %1$d оценки
- Вы получили %1$d оценок
- Вы получили %1$d оценок
+
+ - You received %1$d predicted grade
+ - You received %1$d predicted grades
+ - You received %1$d predicted grades
+ - You received %1$d predicted grades
+
+
+ - You received %1$d final grade
+ - You received %1$d final grades
+ - You received %1$d final grades
+ - You received %1$d final grades
+
Урок
Аудитория
@@ -180,6 +204,8 @@
Перенести в корзину
Удалить навсегда
Сообщение успешно удалено
+ Share
+ Print
Тема
Текст
Сообщение успешно отправлено
@@ -265,6 +291,10 @@
Выйти
Вы точно хотите выйти из данного аккаунта?
Выйти
+ Student account
+ Parent account
+ Mobile API mode
+ Hybrid mode
Версия приложения
Разработчики
@@ -309,7 +339,7 @@
Предыдущий
Следующий
Поиск
- Поиск...
+ Поиск…
Нет уроков
Выбрать тему
diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml
index be2179c3..9942621a 100644
--- a/app/src/main/res/values-uk/preferences_values.xml
+++ b/app/src/main/res/values-uk/preferences_values.xml
@@ -37,7 +37,7 @@
- Середня оцінка з 2 семестру
- Average of grades from both semesters
- - Середня оцінка за весь рік
+ - Average of grades from the whole year
- Не показувати
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index ec4dbc15..423c4e12 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -107,12 +107,36 @@
- Нові оцінки
- Нові оцінки
+
+ - New predicted grade
+ - New predicted grades
+ - New predicted grades
+ - New predicted grades
+
+
+ - New final grade
+ - New final grades
+ - New final grades
+ - New final grades
+
- Ви отримали %1$d оцінку
- Ви отримали %1$d оцінки
- Ви отримали %1$d оцінок
- Ви отримали %1$d оцінок
+
+ - You received %1$d predicted grade
+ - You received %1$d predicted grades
+ - You received %1$d predicted grades
+ - You received %1$d predicted grades
+
+
+ - You received %1$d final grade
+ - You received %1$d final grades
+ - You received %1$d final grades
+ - You received %1$d final grades
+
Урок
Аудиторія
@@ -180,6 +204,8 @@
Перемістити у кошик
Видалити назавжди
Повідомлення було успішно видалено
+ Share
+ Print
Тема
Зміст
Повідомлення було успішно відправлено
@@ -265,6 +291,10 @@
Вийти
Ви впевнені, що хочете вийти з цього аккаунту?
Вийти з аккаунту учня
+ Student account
+ Parent account
+ Mobile API mode
+ Hybrid mode
Версія додатка
Розробники
@@ -309,7 +339,7 @@
Попередній
Наступний
Пошук
- Пошук...
+ Пошук…
Брак уроків
Увібрати тему
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7793cd9c..1eaebf28 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -113,10 +113,26 @@
- New grade
- New grades
+
+ - New predicted grade
+ - New predicted grades
+
+
+ - New final grade
+ - New final grades
+
- You received %1$d grade
- You received %1$d grades
+
+ - You received %1$d predicted grade
+ - You received %1$d predicted grades
+
+
+ - You received %1$d final grade
+ - You received %1$d final grades
+
@@ -194,6 +210,8 @@
Move to trash
Delete permanently
Message deleted successfully
+ Share
+ Print
Subject
Content
Message sent successfully
@@ -282,6 +300,10 @@
Logout
Do you want to log out of an active student?
Student logout
+ Student account
+ Parent account
+ Mobile API mode
+ Hybrid mode
@@ -339,7 +361,7 @@
Prev
Next
Search
- Search...
+ Search…
diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt
index 5d003f27..f7d29220 100644
--- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt
+++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt
@@ -1,5 +1,6 @@
package io.github.wulkanowy
+import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable
@@ -72,3 +73,25 @@ fun getTimetableEntity(
teacher = "",
teacherOld = ""
)
+
+fun getMessageEntity(
+ messageId: Int,
+ content: String,
+ unread: Boolean
+) = Message(
+ studentId = 1,
+ realId = 1,
+ messageId = messageId,
+ sender = "",
+ senderId = 1,
+ recipient = "",
+ subject = "",
+ content = content,
+ date = now(),
+ folderId = 1,
+ unread = unread,
+ unreadBy = 1,
+ readBy = 1,
+ removed = false,
+ hasAttachments = false
+)
diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt
index b7963d43..fcc4188a 100644
--- a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt
@@ -2,10 +2,10 @@ package io.github.wulkanowy.data.repositories.message
import androidx.room.EmptyResultSetException
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
-import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.UnitTestInternetObservingStrategy
+import io.github.wulkanowy.getMessageEntity
import io.reactivex.Single
import io.reactivex.observers.TestObserver
import org.junit.Assert.assertEquals
@@ -15,7 +15,6 @@ import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import org.threeten.bp.LocalDateTime.now
import java.net.UnknownHostException
class MessageRepositoryTest {
@@ -44,7 +43,7 @@ class MessageRepositoryTest {
@Test
fun `throw error when message is not in the db`() {
- val testMessage = Message(1, 1, 1, "", 1, "", "", "", now(), 1, false, 1, 1, false, false)
+ val testMessage = getMessageEntity(1, "", false)
`when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.error(EmptyResultSetException("No message in database")))
val message = repo.getMessage(student, testMessage)
@@ -55,7 +54,7 @@ class MessageRepositoryTest {
@Test
fun `get message when content already in db`() {
- val testMessage = Message(1, 1, 123, "", 1, "", "", "Test", now(), 1, false, 1, 1, false, false)
+ val testMessage = getMessageEntity(123, "Test", false)
val messageWithAttachment = MessageWithAttachment(testMessage, emptyList())
`when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.just(messageWithAttachment))
@@ -67,7 +66,7 @@ class MessageRepositoryTest {
@Test
fun `get message when content in db is empty`() {
- val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, true, 1, 1, false, false)
+ val testMessage = getMessageEntity(123, "", true)
val testMessageWithContent = testMessage.copy(content = "Test")
val mWa = MessageWithAttachment(testMessage, emptyList())
@@ -86,7 +85,7 @@ class MessageRepositoryTest {
@Test
fun `get message when content in db is empty and there is no internet connection`() {
- val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, false, 1, 1, false, false)
+ val testMessage = getMessageEntity(123, "", false)
val messageWithAttachment = MessageWithAttachment(testMessage, emptyList())
testObservingStrategy.isInternetConnection = false
@@ -100,7 +99,7 @@ class MessageRepositoryTest {
@Test
fun `get message when content in db is empty, unread and there is no internet connection`() {
- val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, true, 1, 1, false, false)
+ val testMessage = getMessageEntity(123, "", true)
val messageWithAttachment = MessageWithAttachment(testMessage, emptyList())
testObservingStrategy.isInternetConnection = false
diff --git a/build.gradle b/build.gradle
index 7e5759d1..4de7310b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
buildscript {
ext.kotlin_version = '1.3.72'
- ext.about_libraries = '8.1.6'
+ ext.about_libraries = '8.2.0'
repositories {
mavenCentral()
google()
@@ -13,7 +13,7 @@ buildscript {
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1'
classpath "com.github.triplet.gradle:play-publisher:2.7.5"
- classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8"
+ classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${about_libraries}"
}