From f9474af39ea58f971efea989a0432459a48c82f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 3 Oct 2019 00:46:08 +0200 Subject: [PATCH] Add points to class grades statistics (#512) --- .circleci/config.yml | 2 +- .gitignore | 3 +- .travis.yml | 2 +- app/build.gradle | 2 +- .../16.json | 104 +- .../17.json | 1530 +++++++++++++++++ .../db/migrations/AbstractMigrationTest.kt | 3 +- .../GradeStatisticsLocalTest.kt | 48 +- .../github/wulkanowy/data/RepositoryModule.kt | 4 + .../github/wulkanowy/data/db/AppDatabase.kt | 11 +- .../data/db/dao/GradePointsStatisticsDao.kt | 26 + .../data/db/entities/GradePointsStatistics.kt | 24 + .../data/db/migrations/Migration17.kt | 20 + .../gradestatistics/GradeStatisticsLocal.kt | 46 +- .../gradestatistics/GradeStatisticsRemote.kt | 22 +- .../GradeStatisticsRepository.kt | 17 + .../statistics/GradeStatisticsFragment.kt | 107 +- .../statistics/GradeStatisticsPresenter.kt | 81 +- .../grade/statistics/GradeStatisticsView.kt | 13 +- .../ui/modules/grade/statistics/ViewType.kt | 7 + .../res/layout/fragment_grade_statistics.xml | 17 + app/src/main/res/values-pl/strings.xml | 5 +- app/src/main/res/values/strings.xml | 5 +- .../GradeStatisticsRemoteTest.kt | 32 +- 24 files changed, 2019 insertions(+), 112 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/17.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/GradePointsStatistics.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt diff --git a/.circleci/config.yml b/.circleci/config.yml index cad321125..5b3f5a08a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -116,7 +116,7 @@ jobs: adb shell input keyevent 82 - run: name: Run instrumented tests - command: ./gradlew clean createPlayDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex + command: ./gradlew clean createFdroidDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex - run: name: Collect logs from emulator command: adb logcat -d > ./app/build/reports/logcat_emulator.txt diff --git a/.gitignore b/.gitignore index 6cb549fa5..5c98a4cff 100644 --- a/.gitignore +++ b/.gitignore @@ -110,4 +110,5 @@ Thumbs.db ### AndroidStudio Patch ### -!/gradle/wrapper/gradle-wrapper.jar \ No newline at end of file +!/gradle/wrapper/gradle-wrapper.jar +.idea/jarRepositories.xml diff --git a/.travis.yml b/.travis.yml index 8c94ef86a..7a8fac277 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,7 +50,7 @@ script: - fossa --no-ansi || true #- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon - ./gradlew testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon - - ./gradlew createPlayDebugCoverageReport --stacktrace --daemon + - ./gradlew createFdroidDebugCoverageReport --stacktrace --daemon - ./gradlew jacocoTestReport --stacktrace --daemon - if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else git fetch --unshallow; diff --git a/app/build.gradle b/app/build.gradle index d9070a590..51d83b814 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,7 +123,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:api:23ae9f6" + implementation "io.github.wulkanowy:api:b7cbd9a" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.1.0" diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/16.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/16.json index 12a9322f1..34df45ebe 100644 --- a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/16.json +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/16.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 16, - "identityHash": "92490bb628715605f7a6776d0e730eae", + "identityHash": "1eccdcb09adc922713ef67f298ec77a7", "entities": [ { "tableName": "Students", @@ -838,6 +838,56 @@ "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, `content` TEXT, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `removed` INTEGER NOT NULL)", @@ -1419,62 +1469,12 @@ }, "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": [] } ], "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, '92490bb628715605f7a6776d0e730eae')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1eccdcb09adc922713ef67f298ec77a7')" ] } } \ No newline at end of file diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/17.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/17.json new file mode 100644 index 000000000..8b777b7bf --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/17.json @@ -0,0 +1,1530 @@ +{ + "formatVersion": 1, + "database": { + "version": 17, + "identityHash": "8bcb3c86f1ddbdf7e20cfa8050525b95", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `endpoint` TEXT NOT NULL, `loginType` TEXT NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `student_name` TEXT NOT NULL, `school_id` 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": "endpoint", + "columnName": "endpoint", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "loginType", + "affinity": "TEXT", + "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": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "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, `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, `is_current` 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": "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": "isCurrent", + "columnName": "is_current", + "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, `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": "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, `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)", + "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": "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 + } + ], + "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` INTEGER 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": "INTEGER", + "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, `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": "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, `content` TEXT, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `removed` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "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 + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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, `category` TEXT 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": "category", + "columnName": "category", + "affinity": "TEXT", + "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, `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)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "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 + } + ], + "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": [] + } + ], + "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, '8bcb3c86f1ddbdf7e20cfa8050525b95')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt index b20b6a760..42e098624 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt @@ -27,7 +27,8 @@ abstract class AbstractMigrationTest { Migration13(), Migration14(), Migration15(), - Migration16() + Migration16(), + Migration17() ) .build() // close the database and release any stream resources when the test finishes diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt index 0057a26e5..f8aa58158 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt @@ -4,6 +4,7 @@ import androidx.room.Room 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.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.Semester import org.junit.After @@ -24,7 +25,7 @@ class GradeStatisticsLocalTest { fun createDb() { testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) .build() - gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics) + gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics, testDb.gradePointsStatistics) } @After @@ -63,7 +64,52 @@ class GradeStatisticsLocalTest { assertEquals(stats[0].subject, "Wszystkie") } + @Test + fun saveAndRead_points() { + gradeStatisticsLocal.saveGradesPointsStatistics(listOf( + getGradePointsStatistics("Matematyka", 2, 1), + getGradePointsStatistics("Chemia", 2, 1), + getGradePointsStatistics("Fizyka", 1, 2) + )) + + val stats = gradeStatisticsLocal.getGradesPointsStatistics( + Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1), + "Matematyka" + ).blockingGet() + with(stats) { + assertEquals(subject, "Matematyka") + assertEquals(others, 5.0) + assertEquals(student, 5.0) + } + } + + @Test + fun saveAndRead_subjectEmpty() { + gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) + + val stats = gradeStatisticsLocal.getGradesPointsStatistics( + Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1), + "Matematyka" + ).blockingGet() + assertEquals(null, stats) + } + + @Test + fun saveAndRead_allEmpty() { + gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) + + val stats = gradeStatisticsLocal.getGradesPointsStatistics( + Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1), + "Wszystkie" + ).blockingGet() + assertEquals(null, stats) + } + private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradeStatistics { return GradeStatistics(studentId, semesterId, subject, 5, 5, false) } + + private fun getGradePointsStatistics(subject: String, studentId: Int, semesterId: Int): GradePointsStatistics { + return GradePointsStatistics(studentId, semesterId, subject, 5.0, 5.0) + } } 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 79099689a..b3686f5ec 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -85,6 +85,10 @@ internal class RepositoryModule { @Provides fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics + @Singleton + @Provides + fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatistics + @Singleton @Provides fun provideMessagesDao(database: AppDatabase) = database.messagesDao 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 390c13977..ba6e151ef 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 @@ -11,6 +11,7 @@ import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradeStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.dao.HomeworkDao @@ -30,6 +31,7 @@ import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Homework @@ -51,6 +53,7 @@ import io.github.wulkanowy.data.db.migrations.Migration13 import io.github.wulkanowy.data.db.migrations.Migration14 import io.github.wulkanowy.data.db.migrations.Migration15 import io.github.wulkanowy.data.db.migrations.Migration16 +import io.github.wulkanowy.data.db.migrations.Migration17 import io.github.wulkanowy.data.db.migrations.Migration2 import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration4 @@ -73,6 +76,7 @@ import javax.inject.Singleton Grade::class, GradeSummary::class, GradeStatistics::class, + GradePointsStatistics::class, Message::class, Note::class, Homework::class, @@ -91,7 +95,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 16 + const val VERSION_SCHEMA = 17 fun newInstance(context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database") @@ -113,7 +117,8 @@ abstract class AppDatabase : RoomDatabase() { Migration13(), Migration14(), Migration15(), - Migration16() + Migration16(), + Migration17() ) .build() } @@ -137,6 +142,8 @@ abstract class AppDatabase : RoomDatabase() { abstract val gradeStatistics: GradeStatisticsDao + abstract val gradePointsStatistics: GradePointsStatisticsDao + abstract val messagesDao: MessagesDao abstract val noteDao: NoteDao diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt new file mode 100644 index 000000000..99a1269bd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.GradePointsStatistics +import io.reactivex.Maybe +import javax.inject.Singleton + +@Singleton +@Dao +interface GradePointsStatisticsDao { + + @Insert + fun insertAll(gradesStatistics: List) + + @Delete + fun deleteAll(gradesStatistics: List) + + @Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName") + fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Maybe + + @Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId") + fun loadAll(semesterId: Int, studentId: Int): Maybe> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradePointsStatistics.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradePointsStatistics.kt new file mode 100644 index 000000000..407f18bd6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradePointsStatistics.kt @@ -0,0 +1,24 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "GradesPointsStatistics") +data class GradePointsStatistics( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "semester_id") + val semesterId: Int, + + val subject: String, + + val others: Double, + + val student: Double +) { + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt new file mode 100644 index 000000000..9276ed471 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration17 : Migration(16, 17) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL(""" + CREATE TABLE IF NOT EXISTS GradesPointsStatistics( + 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 + ) + """) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt index 581ac2f81..d636b91ac 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt @@ -1,6 +1,8 @@ package io.github.wulkanowy.data.repositories.gradestatistics +import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradeStatisticsDao +import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.Semester import io.reactivex.Maybe @@ -8,27 +10,57 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class GradeStatisticsLocal @Inject constructor(private val gradeStatisticsDb: GradeStatisticsDao) { +class GradeStatisticsLocal @Inject constructor( + private val gradeStatisticsDb: GradeStatisticsDao, + private val gradePointsStatisticsDb: GradePointsStatisticsDao +) { fun getGradesStatistics(semester: Semester, isSemester: Boolean): Maybe> { - return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester) - .filter { !it.isEmpty() } + return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).filter { it.isNotEmpty() } + } + + fun getGradesPointsStatistics(semester: Semester): Maybe> { + return gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } } fun getGradesStatistics(semester: Semester, isSemester: Boolean, subjectName: String): Maybe> { - return (if ("Wszystkie" == subjectName) gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).map { list -> - list.groupBy { it.grade }.map { - GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, it.value.fold(0) { acc, e -> acc + e.amount }, false) + return when (subjectName) { + "Wszystkie" -> gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).map { list -> + list.groupBy { it.grade }.map { + GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, + it.value.fold(0) { acc, e -> acc + e.amount }, false) + } } + else -> gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester) + }.filter { it.isNotEmpty() } + } + + fun getGradesPointsStatistics(semester: Semester, subjectName: String): Maybe { + return when (subjectName) { + "Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).flatMap { list -> + if (list.isEmpty()) return@flatMap Maybe.empty() + Maybe.just(GradePointsStatistics(semester.studentId, semester.semesterId, subjectName, + list.fold(.0) { acc, e -> acc + e.others }, + list.fold(.0) { acc, e -> acc + e.student }) + ) + } + else -> gradePointsStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName) } - else gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester)).filter { !it.isEmpty() } } fun saveGradesStatistics(gradesStatistics: List) { gradeStatisticsDb.insertAll(gradesStatistics) } + fun saveGradesPointsStatistics(gradePointsStatistics: List) { + gradePointsStatisticsDb.insertAll(gradePointsStatistics) + } + fun deleteGradesStatistics(gradesStatistics: List) { gradeStatisticsDb.deleteAll(gradesStatistics) } + + fun deleteGradesPointsStatistics(gradesPointsStatistics: List) { + gradePointsStatisticsDb.deleteAll(gradesPointsStatistics) + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt index fa3b951f6..74c1f7357 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories.gradestatistics import io.github.wulkanowy.api.Api +import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.Semester import io.reactivex.Single @@ -12,7 +13,10 @@ class GradeStatisticsRemote @Inject constructor(private val api: Api) { fun getGradeStatistics(semester: Semester, isSemester: Boolean): Single> { return Single.just(api.apply { diaryId = semester.diaryId }) - .flatMap { it.getGradesStatistics(semester.semesterId, isSemester) } + .flatMap { + if (isSemester) it.getGradesAnnualStatistics(semester.semesterId) + else it.getGradesPartialStatistics(semester.semesterId) + } .map { gradeStatistics -> gradeStatistics.map { GradeStatistics( @@ -26,4 +30,20 @@ class GradeStatisticsRemote @Inject constructor(private val api: Api) { } } } + + fun getGradePointsStatistics(semester: Semester): Single> { + return Single.just(api.apply { diaryId = semester.diaryId }) + .flatMap { it.getGradesPointsStatistics(semester.semesterId) } + .map { gradePointsStatistics -> + gradePointsStatistics.map { + GradePointsStatistics( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + others = it.others, + student = it.student + ) + } + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt index 9617cdd64..9335d1d21 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt @@ -2,9 +2,11 @@ package io.github.wulkanowy.data.repositories.gradestatistics import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings +import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.utils.uniqueSubtract +import io.reactivex.Maybe import io.reactivex.Single import java.net.UnknownHostException import javax.inject.Inject @@ -31,4 +33,19 @@ class GradeStatisticsRepository @Inject constructor( } }.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).toSingle(emptyList()) }) } + + fun getGradesPointsStatistics(semester: Semester, subjectName: String, forceRefresh: Boolean): Maybe { + return local.getGradesPointsStatistics(semester, subjectName).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMapMaybe { + if (it) remote.getGradePointsStatistics(semester).toMaybe() + else Maybe.error(UnknownHostException()) + }.flatMap { new -> + local.getGradesPointsStatistics(semester).defaultIfEmpty(emptyList()) + .doOnSuccess { old -> + local.deleteGradesPointsStatistics(old.uniqueSubtract(new)) + local.saveGradesPointsStatistics(new.uniqueSubtract(old)) + } + }.flatMap { local.getGradesPointsStatistics(semester, subjectName) }) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt index 4b6c16759..94b5f474b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.statistics +import android.graphics.Color import android.graphics.Color.WHITE import android.os.Bundle import android.view.LayoutInflater @@ -10,11 +11,15 @@ import android.widget.TextView import androidx.core.content.ContextCompat import com.github.mikephil.charting.components.Legend import com.github.mikephil.charting.components.LegendEntry +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry import com.github.mikephil.charting.data.PieData import com.github.mikephil.charting.data.PieDataSet import com.github.mikephil.charting.data.PieEntry import com.github.mikephil.charting.formatter.ValueFormatter import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment @@ -38,7 +43,9 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G fun newInstance() = GradeStatisticsFragment() } - override val isViewEmpty get() = gradeStatisticsChart.isEmpty + override val isPieViewEmpty get() = gradeStatisticsChart.isEmpty + + override val isBarViewEmpty get() = gradeStatisticsChartPoints.isEmpty private lateinit var gradeColors: List> @@ -60,6 +67,11 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G 1 to R.color.grade_material_one ) + private val gradePointsColors = listOf( + Color.parseColor("#37c69c"), + Color.parseColor("#d8b12a") + ) + private val gradeLabels = listOf( "6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+" ) @@ -70,8 +82,8 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - messageContainer = gradeStatisticsChart - presenter.onAttachView(this, savedInstanceState?.getBoolean(SAVED_CHART_TYPE)) + messageContainer = gradeStatisticsSwipe + presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? ViewType) } override fun initView() { @@ -84,6 +96,13 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary) } + with(gradeStatisticsChartPoints) { + description.isEnabled = false + + animateXY(1000, 1000) + legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary) + } + subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf()) subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject) @@ -105,23 +124,25 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } } - override fun updateData(items: List, theme: String) { + override fun updatePieData(items: List, theme: String) { gradeColors = when (theme) { "vulcan" -> vulcanGradeColors else -> materialGradeColors } - gradeStatisticsChart.run { - data = PieData(PieDataSet(items.map { - PieEntry(it.amount.toFloat(), it.grade.toString()) - }, "Legenda").apply { - valueTextSize = 12f - sliceSpace = 1f - valueTextColor = WHITE - setColors(items.map { - gradeColors.single { color -> color.first == it.grade }.second - }.toIntArray(), context) - }).apply { + val dataset = PieDataSet(items.map { + PieEntry(it.amount.toFloat(), it.grade.toString()) + }, "Legenda").apply { + valueTextSize = 12f + sliceSpace = 1f + valueTextColor = WHITE + setColors(items.map { + gradeColors.single { color -> color.first == it.grade }.second + }.toIntArray(), context) + } + + with(gradeStatisticsChart) { + data = PieData(dataset).apply { setTouchEnabled(false) setValueFormatter(object : ValueFormatter() { override fun getPieLabel(value: Float, pieEntry: PieEntry): String { @@ -144,6 +165,47 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } } + override fun updateBarData(item: GradePointsStatistics) { + val dataset = BarDataSet(listOf( + BarEntry(1f, item.others.toFloat()), + BarEntry(2f, item.student.toFloat()) + ), "Legenda").apply { + valueTextSize = 12f + valueTextColor = requireContext().getThemeAttrColor(android.R.attr.textColorPrimary) + valueFormatter = object : ValueFormatter() { + override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%" + } + colors = gradePointsColors + } + + with(gradeStatisticsChartPoints) { + data = BarData(dataset).apply { + barWidth = 0.5f + setFitBars(true) + } + setTouchEnabled(false) + xAxis.setDrawLabels(false) + xAxis.setDrawGridLines(false) + requireContext().getThemeAttrColor(android.R.attr.textColorPrimary).let { + axisLeft.textColor = it + axisRight.textColor = it + } + legend.setCustom(listOf( + LegendEntry().apply { + label = "Średnia klasy" + formColor = gradePointsColors[0] + form = Legend.LegendForm.SQUARE + }, + LegendEntry().apply { + label = "Uczeń" + formColor = gradePointsColors[1] + form = Legend.LegendForm.SQUARE + } + )) + invalidate() + } + } + override fun showSubjects(show: Boolean) { gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE gradeStatisticsTypeSwitch.visibility = if (show) View.VISIBLE else View.INVISIBLE @@ -151,12 +213,17 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G override fun clearView() { gradeStatisticsChart.clear() + gradeStatisticsChartPoints.clear() } - override fun showContent(show: Boolean) { + override fun showPieContent(show: Boolean) { gradeStatisticsChart.visibility = if (show) View.VISIBLE else View.GONE } + override fun showBarContent(show: Boolean) { + gradeStatisticsChartPoints.visibility = if (show) View.VISIBLE else View.GONE + } + override fun showEmpty(show: Boolean) { gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE } @@ -196,13 +263,17 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G override fun onResume() { super.onResume() gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, checkedId -> - presenter.onTypeChange(checkedId == R.id.gradeStatisticsTypeSemester) + presenter.onTypeChange(when (checkedId) { + R.id.gradeStatisticsTypeSemester -> ViewType.SEMESTER + R.id.gradeStatisticsTypePartial -> ViewType.PARTIAL + else -> ViewType.POINTS + }) } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putBoolean(SAVED_CHART_TYPE, presenter.currentIsSemester) + outState.putSerializable(SAVED_CHART_TYPE, presenter.currentType) } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index 0259557a7..1e0537016 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -30,19 +30,19 @@ class GradeStatisticsPresenter @Inject constructor( private var currentSubjectName: String = "Wszystkie" - var currentIsSemester = false + var currentType: ViewType = ViewType.PARTIAL private set - fun onAttachView(view: GradeStatisticsView, isSemester: Boolean?) { + fun onAttachView(view: GradeStatisticsView, type: ViewType?) { super.onAttachView(view) - currentIsSemester = isSemester ?: false + currentType = type ?: ViewType.PARTIAL view.initView() } fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { currentSemesterId = semesterId loadSubjects() - loadData(semesterId, currentSubjectName, currentIsSemester, forceRefresh) + loadDataByType(semesterId, currentSubjectName, currentType, forceRefresh) } fun onParentViewChangeSemester() { @@ -50,7 +50,7 @@ class GradeStatisticsPresenter @Inject constructor( showProgress(true) enableSwipe(false) showRefresh(false) - showContent(false) + showBarContent(false) showEmpty(false) clearView() } @@ -65,28 +65,30 @@ class GradeStatisticsPresenter @Inject constructor( fun onSubjectSelected(name: String?) { Timber.i("Select grade stats subject $name") view?.run { - showContent(false) + showBarContent(false) + showPieContent(false) showProgress(true) enableSwipe(false) showEmpty(false) clearView() } (subjects.singleOrNull { it.name == name }?.name)?.let { - if (it != currentSubjectName) loadData(currentSemesterId, it, currentIsSemester) + if (it != currentSubjectName) loadDataByType(currentSemesterId, it, currentType) } } - fun onTypeChange(isSemester: Boolean) { - Timber.i("Select grade stats semester: $isSemester") + fun onTypeChange(type: ViewType) { + Timber.i("Select grade stats semester: $type") disposable.clear() view?.run { - showContent(false) + showBarContent(false) + showPieContent(false) showProgress(true) enableSwipe(false) showEmpty(false) clearView() } - loadData(currentSemesterId, currentSubjectName, isSemester) + loadDataByType(currentSemesterId, currentSubjectName, type) } private fun loadSubjects() { @@ -111,10 +113,18 @@ class GradeStatisticsPresenter @Inject constructor( ) } + private fun loadDataByType(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean = false) { + currentSubjectName = subjectName + currentType = type + when (type) { + ViewType.SEMESTER -> loadData(semesterId, subjectName, true, forceRefresh) + ViewType.PARTIAL -> loadData(semesterId, subjectName, false, forceRefresh) + ViewType.POINTS -> loadPointsData(semesterId, subjectName, forceRefresh) + } + } + private fun loadData(semesterId: Int, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false) { Timber.i("Loading grade stats data started") - currentSubjectName = subjectName - currentIsSemester = isSemester disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getSemesters(it) } .flatMap { gradeStatisticsRepository.getGradesStatistics(it.first { item -> item.semesterId == semesterId }, subjectName, isSemester, forceRefresh) } @@ -134,14 +144,53 @@ class GradeStatisticsPresenter @Inject constructor( Timber.i("Loading grade stats result: Success") view?.run { showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - updateData(it, preferencesRepository.gradeColorTheme) + showBarContent(false) + showPieContent(it.isNotEmpty()) + updatePieData(it, preferencesRepository.gradeColorTheme) } analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh) }) { Timber.e("Loading grade stats result: An exception occurred") - view?.run { showEmpty(isViewEmpty) } + view?.run { showEmpty(isPieViewEmpty) } errorHandler.dispatch(it) }) } + + private fun loadPointsData(semesterId: Int, subjectName: String, forceRefresh: Boolean = false) { + Timber.i("Loading grade points stats data started") + disposable.add(studentRepository.getCurrentStudent() + .flatMap { semesterRepository.getSemesters(it) } + .flatMapMaybe { gradeStatisticsRepository.getGradesPointsStatistics(it.first { item -> item.semesterId == semesterId }, subjectName, forceRefresh) } + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .doFinally { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded(semesterId) + } + } + .subscribe({ + Timber.i("Loading grade points stats result: Success") + view?.run { + showEmpty(false) + showPieContent(false) + showBarContent(true) + updateBarData(it) + } + analytics.logEvent("load_grade_points_statistics", "force_refresh" to forceRefresh) + }, { + Timber.e("Loading grade points stats result: An exception occurred") + view?.run { showEmpty(isBarViewEmpty) } + errorHandler.dispatch(it) + }, { + Timber.d("Loading grade points stats result: No point stats found") + view?.run { + showBarContent(false) + showEmpty(true) + } + }) + ) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt index edb2552e0..d6e66fac3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt @@ -1,17 +1,22 @@ package io.github.wulkanowy.ui.modules.grade.statistics +import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.ui.base.BaseView interface GradeStatisticsView : BaseView { - val isViewEmpty: Boolean + val isPieViewEmpty: Boolean + + val isBarViewEmpty: Boolean fun initView() fun updateSubjects(data: ArrayList) - fun updateData(items: List, theme: String) + fun updatePieData(items: List, theme: String) + + fun updateBarData(item: GradePointsStatistics) fun showSubjects(show: Boolean) @@ -21,7 +26,9 @@ interface GradeStatisticsView : BaseView { fun clearView() - fun showContent(show: Boolean) + fun showPieContent(show: Boolean) + + fun showBarContent(show: Boolean) fun showEmpty(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt new file mode 100644 index 000000000..08c37d587 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/ViewType.kt @@ -0,0 +1,7 @@ +package io.github.wulkanowy.ui.modules.grade.statistics + +enum class ViewType { + SEMESTER, + PARTIAL, + POINTS +} diff --git a/app/src/main/res/layout/fragment_grade_statistics.xml b/app/src/main/res/layout/fragment_grade_statistics.xml index 49ae6682b..6e88f8a89 100644 --- a/app/src/main/res/layout/fragment_grade_statistics.xml +++ b/app/src/main/res/layout/fragment_grade_statistics.xml @@ -72,6 +72,13 @@ android:layout_height="wrap_content" android:tag="annual" android:text="@string/grade_statistics_semester" /> + + + + Podsumowanie Klasa Oznacz jako przeczytane - Oceny cząstkowe - Oceny semestralne + Cząstkowe + Semestralne + Punkty %d ocena %d oceny diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b071656fe..27e43dc5d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,8 +66,9 @@ Summary Class Mark as read - Partial grades - Semester grades + Partial + Semester + Points %d grade %d grades diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt index 37d3f2ad6..2fb566e7c 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories.gradestatistics import io.github.wulkanowy.api.Api +import io.github.wulkanowy.api.grades.GradePointsSummary import io.github.wulkanowy.api.grades.GradeStatistics import io.github.wulkanowy.data.db.entities.Semester import io.mockk.MockKAnnotations @@ -8,7 +9,7 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK import io.reactivex.Single -import org.junit.Assert +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -27,7 +28,7 @@ class GradeStatisticsRemoteTest { @Test fun getGradeStatisticsTest() { - every { mockApi.getGradesStatistics(1, any()) } returns Single.just(listOf( + every { mockApi.getGradesPartialStatistics(1) } returns Single.just(listOf( getGradeStatistics("Fizyka"), getGradeStatistics("Matematyka") )) @@ -39,7 +40,24 @@ class GradeStatisticsRemoteTest { every { semesterMock.diaryId } returns 1 val stats = GradeStatisticsRemote(mockApi).getGradeStatistics(semesterMock, false).blockingGet() - Assert.assertEquals(2, stats.size) + assertEquals(2, stats.size) + } + + @Test + fun getGradePointsStatisticsTest() { + every { mockApi.getGradesPointsStatistics(1) } returns Single.just(listOf( + getGradePointsStatistics("Fizyka"), + getGradePointsStatistics("Matematyka") + )) + + every { mockApi.diaryId } returns 1 + every { semesterMock.studentId } returns 1 + every { semesterMock.semesterId } returns 1 + every { semesterMock.semesterName } returns 2 + every { semesterMock.diaryId } returns 1 + + val stats = GradeStatisticsRemote(mockApi).getGradePointsStatistics(semesterMock).blockingGet() + assertEquals(2, stats.size) } private fun getGradeStatistics(subjectName: String): GradeStatistics { @@ -49,4 +67,12 @@ class GradeStatisticsRemoteTest { amount = 10 } } + + private fun getGradePointsStatistics(subjectName: String): GradePointsSummary { + return GradePointsSummary( + subject = subjectName, + student = 0.80, + others = 0.40 + ) + } }