Add average in class grades statistics (#1017)

This commit is contained in:
Mikołaj Pich 2020-11-11 16:03:52 +01:00 committed by GitHub
parent ada5854d10
commit a1ebf6c6ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2270 additions and 145 deletions

View File

@ -142,7 +142,7 @@ configurations.all {
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:0.22.2" implementation "io.github.wulkanowy:sdk:bcf6b53"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,8 @@ import androidx.room.Room
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics 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.data.db.entities.Semester
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -27,7 +27,7 @@ class GradeStatisticsLocalTest {
fun createDb() { fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build() .build()
gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics, testDb.gradePointsStatistics) gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradePartialStatisticsDao, testDb.gradePointsStatisticsDao, testDb.gradeSemesterStatisticsDao)
} }
@After @After
@ -41,9 +41,9 @@ class GradeStatisticsLocalTest {
getGradeStatistics("Matematyka", 2, 1), getGradeStatistics("Matematyka", 2, 1),
getGradeStatistics("Fizyka", 1, 2) getGradeStatistics("Fizyka", 1, 2)
) )
runBlocking { gradeStatisticsLocal.saveGradesStatistics(list) } runBlocking { gradeStatisticsLocal.saveGradePartialStatistics(list) }
val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false).first() } val stats = runBlocking { gradeStatisticsLocal.getGradePartialStatistics(getSemester()).first() }
assertEquals(1, stats.size) assertEquals(1, stats.size)
assertEquals(stats[0].subject, "Matematyka") assertEquals(stats[0].subject, "Matematyka")
} }
@ -55,12 +55,10 @@ class GradeStatisticsLocalTest {
getGradeStatistics("Chemia", 2, 1), getGradeStatistics("Chemia", 2, 1),
getGradeStatistics("Fizyka", 1, 2) getGradeStatistics("Fizyka", 1, 2)
) )
runBlocking { gradeStatisticsLocal.saveGradesStatistics(list) } runBlocking { gradeStatisticsLocal.saveGradePartialStatistics(list) }
val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false).first() } val stats = runBlocking { gradeStatisticsLocal.getGradePartialStatistics(getSemester()).first() }
assertEquals(2, stats.size) assertEquals(2, stats.size)
// assertEquals(3, stats.size)
// assertEquals(stats[0].subject, "Wszystkie") // now in main repo
assertEquals(stats[0].subject, "Matematyka") assertEquals(stats[0].subject, "Matematyka")
assertEquals(stats[1].subject, "Chemia") assertEquals(stats[1].subject, "Chemia")
} }
@ -72,9 +70,9 @@ class GradeStatisticsLocalTest {
getGradePointsStatistics("Chemia", 2, 1), getGradePointsStatistics("Chemia", 2, 1),
getGradePointsStatistics("Fizyka", 1, 2) getGradePointsStatistics("Fizyka", 1, 2)
) )
runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(list) } runBlocking { gradeStatisticsLocal.saveGradePointsStatistics(list) }
val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() } val stats = runBlocking { gradeStatisticsLocal.getGradePointsStatistics(getSemester()).first() }
with(stats[0]) { with(stats[0]) {
assertEquals(subject, "Matematyka") assertEquals(subject, "Matematyka")
assertEquals(others, 5.0) assertEquals(others, 5.0)
@ -84,17 +82,17 @@ class GradeStatisticsLocalTest {
@Test @Test
fun saveAndRead_subjectEmpty() { fun saveAndRead_subjectEmpty() {
runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) } runBlocking { gradeStatisticsLocal.saveGradePointsStatistics(listOf()) }
val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() } val stats = runBlocking { gradeStatisticsLocal.getGradePointsStatistics(getSemester()).first() }
assertEquals(emptyList(), stats) assertEquals(emptyList(), stats)
} }
@Test @Test
fun saveAndRead_allEmpty() { fun saveAndRead_allEmpty() {
runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) } runBlocking { gradeStatisticsLocal.saveGradePointsStatistics(listOf()) }
val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() } val stats = runBlocking { gradeStatisticsLocal.getGradePointsStatistics(getSemester()).first() }
assertEquals(emptyList(), stats) assertEquals(emptyList(), stats)
} }
@ -102,8 +100,8 @@ class GradeStatisticsLocalTest {
return Semester(2, 2, "", 2019, 1, 2, now(), now(), 1, 1) return Semester(2, 2, "", 2019, 1, 2, now(), now(), 1, 1)
} }
private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradeStatistics { private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradePartialStatistics {
return GradeStatistics(studentId, semesterId, subject, 5, 5, false) return GradePartialStatistics(studentId, semesterId, subject, "", "", listOf(5), listOf(5))
} }
private fun getGradePointsStatistics(subject: String, studentId: Int, semesterId: Int): GradePointsStatistics { private fun getGradePointsStatistics(subject: String, studentId: Int, semesterId: Int): GradePointsStatistics {

View File

@ -42,7 +42,7 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR) {
connectCrash.setCustomKey("priority", priority) connectCrash.setCustomKey("priority", priority)
connectCrash.setCustomKey("tag", tag.orEmpty()) connectCrash.setCustomKey("tag", tag.orEmpty())
connectCrash.setCustomKey("message", message) connectCrash.setCustomKey("message", message)
connectCrash.log(priority, t?.stackTraceToString())
if (t != null) { if (t != null) {
connectCrash.log(priority, t.stackTraceToString()) connectCrash.log(priority, t.stackTraceToString())
} else { } else {

View File

@ -85,11 +85,15 @@ internal class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics fun provideGradePartialStatisticsDao(database: AppDatabase) = database.gradePartialStatisticsDao
@Singleton @Singleton
@Provides @Provides
fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatistics fun provideGradeSemesterStatisticsDao(database: AppDatabase) = database.gradeSemesterStatisticsDao
@Singleton
@Provides
fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatisticsDao
@Singleton @Singleton
@Provides @Provides

View File

@ -13,8 +13,9 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.db.dao.LuckyNumberDao
@ -36,8 +37,9 @@ import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
@ -73,6 +75,7 @@ import io.github.wulkanowy.data.db.migrations.Migration25
import io.github.wulkanowy.data.db.migrations.Migration26 import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration27 import io.github.wulkanowy.data.db.migrations.Migration27
import io.github.wulkanowy.data.db.migrations.Migration28 import io.github.wulkanowy.data.db.migrations.Migration28
import io.github.wulkanowy.data.db.migrations.Migration29
import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration5
@ -93,8 +96,9 @@ import javax.inject.Singleton
AttendanceSummary::class, AttendanceSummary::class,
Grade::class, Grade::class,
GradeSummary::class, GradeSummary::class,
GradeStatistics::class, GradePartialStatistics::class,
GradePointsStatistics::class, GradePointsStatistics::class,
GradeSemesterStatistics::class,
Message::class, Message::class,
MessageAttachment::class, MessageAttachment::class,
Note::class, Note::class,
@ -116,7 +120,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 28 const val VERSION_SCHEMA = 29
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> { fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
return arrayOf( return arrayOf(
@ -147,6 +151,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration26(), Migration26(),
Migration27(), Migration27(),
Migration28(), Migration28(),
Migration29()
) )
} }
@ -176,9 +181,11 @@ abstract class AppDatabase : RoomDatabase() {
abstract val gradeSummaryDao: GradeSummaryDao abstract val gradeSummaryDao: GradeSummaryDao
abstract val gradeStatistics: GradeStatisticsDao abstract val gradePartialStatisticsDao: GradePartialStatisticsDao
abstract val gradePointsStatistics: GradePointsStatisticsDao abstract val gradePointsStatisticsDao: GradePointsStatisticsDao
abstract val gradeSemesterStatisticsDao: GradeSemesterStatisticsDao
abstract val messagesDao: MessagesDao abstract val messagesDao: MessagesDao

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import kotlinx.coroutines.flow.Flow
@Dao
interface GradePartialStatisticsDao : BaseDao<GradePartialStatistics> {
@Query("SELECT * FROM GradePartialStatistics WHERE student_id = :studentId AND semester_id = :semesterId")
fun loadAll(semesterId: Int, studentId: Int): Flow<List<GradePartialStatistics>>
}

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import kotlinx.coroutines.flow.Flow
@Dao
interface GradeSemesterStatisticsDao : BaseDao<GradeSemesterStatistics> {
@Query("SELECT * FROM GradeSemesterStatistics WHERE student_id = :studentId AND semester_id = :semesterId")
fun loadAll(semesterId: Int, studentId: Int): Flow<List<GradeSemesterStatistics>>
}

View File

@ -1,18 +0,0 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradeStatistics
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Singleton
@Dao
interface GradeStatisticsDao : BaseDao<GradeStatistics> {
@Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName AND is_semester = :isSemester")
fun loadSubject(semesterId: Int, studentId: Int, subjectName: String, isSemester: Boolean): Flow<List<GradeStatistics>>
@Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND is_semester = :isSemester")
fun loadAll(semesterId: Int, studentId: Int, isSemester: Boolean): Flow<List<GradeStatistics>>
}

View File

@ -0,0 +1,33 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "GradePartialStatistics")
data class GradePartialStatistics(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "semester_id")
val semesterId: Int,
val subject: String,
@ColumnInfo(name = "class_average")
val classAverage: String,
@ColumnInfo(name = "student_average")
val studentAverage: String,
@ColumnInfo(name = "class_amounts")
val classAmounts: List<Int>,
@ColumnInfo(name = "student_amounts")
val studentAmounts: List<Int>
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -4,8 +4,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity(tableName = "GradesStatistics") @Entity(tableName = "GradeSemesterStatistics")
data class GradeStatistics( data class GradeSemesterStatistics(
@ColumnInfo(name = "student_id") @ColumnInfo(name = "student_id")
val studentId: Int, val studentId: Int,
@ -15,13 +15,14 @@ data class GradeStatistics(
val subject: String, val subject: String,
val grade: Int, val amounts: List<Int>,
val amount: Int, @ColumnInfo(name = "student_grade")
val studentGrade: Int
@ColumnInfo(name = "is_semester")
val semester: Boolean
) { ) {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
@Transient
var average: String = ""
} }

View File

@ -0,0 +1,33 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration29 : Migration(28, 29) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS GradesStatistics")
database.execSQL("""
CREATE TABLE IF NOT EXISTS GradeSemesterStatistics (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
semester_id INTEGER NOT NULL,
subject TEXT NOT NULL,
amounts TEXT NOT NULL,
student_grade INTEGER NOT NULL
)
""")
database.execSQL("""
CREATE TABLE IF NOT EXISTS GradePartialStatistics (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
semester_id INTEGER NOT NULL,
subject TEXT NOT NULL,
class_average TEXT NOT NULL,
student_average TEXT NOT NULL,
class_amounts TEXT NOT NULL,
student_amounts TEXT NOT NULL
)
""")
}
}

View File

@ -1,14 +1,19 @@
package io.github.wulkanowy.data.pojos package io.github.wulkanowy.data.pojos
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.ui.modules.grade.statistics.ViewType import io.github.wulkanowy.ui.modules.grade.statistics.ViewType
data class GradeStatisticsItem( data class GradeStatisticsItem(
val type: ViewType, val type: ViewType,
val partial: List<GradeStatistics>, val average: String,
val partial: GradePartialStatistics?,
val semester: GradeSemesterStatistics?,
val points: GradePointsStatistics? val points: GradePointsStatistics?
) )

View File

@ -1,9 +1,11 @@
package io.github.wulkanowy.data.repositories.gradestatistics package io.github.wulkanowy.data.repositories.gradestatistics
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Inject import javax.inject.Inject
@ -11,31 +13,47 @@ import javax.inject.Singleton
@Singleton @Singleton
class GradeStatisticsLocal @Inject constructor( class GradeStatisticsLocal @Inject constructor(
private val gradeStatisticsDb: GradeStatisticsDao, private val gradePartialStatisticsDb: GradePartialStatisticsDao,
private val gradePointsStatisticsDb: GradePointsStatisticsDao private val gradePointsStatisticsDb: GradePointsStatisticsDao,
private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao
) { ) {
fun getGradesStatistics(semester: Semester, isSemester: Boolean): Flow<List<GradeStatistics>> { // partial
return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester) fun getGradePartialStatistics(semester: Semester): Flow<List<GradePartialStatistics>> {
return gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId)
} }
fun getGradesPointsStatistics(semester: Semester): Flow<List<GradePointsStatistics>> { suspend fun saveGradePartialStatistics(items: List<GradePartialStatistics>) {
gradePartialStatisticsDb.insertAll(items)
}
suspend fun deleteGradePartialStatistics(items: List<GradePartialStatistics>) {
gradePartialStatisticsDb.deleteAll(items)
}
// points
fun getGradePointsStatistics(semester: Semester): Flow<List<GradePointsStatistics>> {
return gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) return gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId)
} }
suspend fun saveGradesStatistics(gradesStatistics: List<GradeStatistics>) { suspend fun saveGradePointsStatistics(gradePointsStatistics: List<GradePointsStatistics>) {
gradeStatisticsDb.insertAll(gradesStatistics)
}
suspend fun saveGradesPointsStatistics(gradePointsStatistics: List<GradePointsStatistics>) {
gradePointsStatisticsDb.insertAll(gradePointsStatistics) gradePointsStatisticsDb.insertAll(gradePointsStatistics)
} }
suspend fun deleteGradesStatistics(gradesStatistics: List<GradeStatistics>) { suspend fun deleteGradePointsStatistics(gradesPointsStatistics: List<GradePointsStatistics>) {
gradeStatisticsDb.deleteAll(gradesStatistics)
}
suspend fun deleteGradesPointsStatistics(gradesPointsStatistics: List<GradePointsStatistics>) {
gradePointsStatisticsDb.deleteAll(gradesPointsStatistics) gradePointsStatisticsDb.deleteAll(gradesPointsStatistics)
} }
// semester
fun getGradeSemesterStatistics(semester: Semester): Flow<List<GradeSemesterStatistics>> {
return gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId)
}
suspend fun saveGradeSemesterStatistics(items: List<GradeSemesterStatistics>) {
gradeSemesterStatisticsDb.insertAll(items)
}
suspend fun deleteGradeSemesterStatistics(items: List<GradeSemesterStatistics>) {
gradeSemesterStatisticsDb.deleteAll(items)
}
} }

View File

@ -1,7 +1,8 @@
package io.github.wulkanowy.data.repositories.gradestatistics package io.github.wulkanowy.data.repositories.gradestatistics
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
@ -12,18 +13,36 @@ import javax.inject.Singleton
@Singleton @Singleton
class GradeStatisticsRemote @Inject constructor(private val sdk: Sdk) { class GradeStatisticsRemote @Inject constructor(private val sdk: Sdk) {
suspend fun getGradeStatistics(student: Student, semester: Semester, isSemester: Boolean): List<GradeStatistics> { suspend fun getGradePartialStatistics(student: Student, semester: Semester): List<GradePartialStatistics> {
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).let { return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
if (isSemester) it.getGradesAnnualStatistics(semester.semesterId) .getGradesPartialStatistics(semester.semesterId)
else it.getGradesPartialStatistics(semester.semesterId) .map {
}.map { GradePartialStatistics(
GradeStatistics( semesterId = semester.semesterId,
studentId = student.studentId,
subject = it.subject,
classAverage = it.classAverage,
studentAverage = it.studentAverage,
classAmounts = it.classItems
.sortedBy { item -> item.grade }
.map { item -> item.amount },
studentAmounts = it.studentItems.map { item -> item.amount }
)
}
}
suspend fun getGradeSemesterStatistics(student: Student, semester: Semester): List<GradeSemesterStatistics> {
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getGradesSemesterStatistics(semester.semesterId)
.map {
GradeSemesterStatistics(
semesterId = semester.semesterId, semesterId = semester.semesterId,
studentId = semester.studentId, studentId = semester.studentId,
subject = it.subject, subject = it.subject,
grade = it.gradeValue, amounts = it.items
amount = it.amount, .sortedBy { item -> item.grade }
semester = isSemester .map { item -> item.amount },
studentGrade = it.items.singleOrNull { item -> item.isStudentHere }?.grade ?: 0
) )
} }
} }

View File

@ -1,13 +1,15 @@
package io.github.wulkanowy.data.repositories.gradestatistics package io.github.wulkanowy.data.repositories.gradestatistics
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.data.pojos.GradeStatisticsItem
import io.github.wulkanowy.ui.modules.grade.statistics.ViewType import io.github.wulkanowy.ui.modules.grade.statistics.ViewType
import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -17,55 +19,125 @@ class GradeStatisticsRepository @Inject constructor(
private val remote: GradeStatisticsRemote private val remote: GradeStatisticsRemote
) { ) {
fun getGradesStatistics(student: Student, semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean) = networkBoundResource( fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
shouldFetch = { it.isEmpty() || forceRefresh }, shouldFetch = { it.isEmpty() || forceRefresh },
query = { local.getGradesStatistics(semester, isSemester) }, query = { local.getGradePartialStatistics(semester) },
fetch = { remote.getGradeStatistics(student, semester, isSemester) }, fetch = { remote.getGradePartialStatistics(student, semester) },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
local.deleteGradesStatistics(old uniqueSubtract new) local.deleteGradePartialStatistics(old uniqueSubtract new)
local.saveGradesStatistics(new uniqueSubtract old) local.saveGradePartialStatistics(new uniqueSubtract old)
}, },
mapResult = { items -> mapResult = { items ->
when (subjectName) { when (subjectName) {
"Wszystkie" -> items.groupBy { it.grade }.map { "Wszystkie" -> {
GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, val numerator = items.map {
it.value.fold(0) { acc, e -> acc + e.amount }, false) it.classAverage.replace(",", ".").toDoubleOrNull() ?: .0
} + items }.filterNot { it == .0 }
(items.reversed() + GradePartialStatistics(
studentId = semester.studentId,
semesterId = semester.semesterId,
subject = subjectName,
classAverage = if (numerator.isEmpty()) "" else numerator.average().let {
"%.2f".format(Locale.FRANCE, it)
},
studentAverage = "",
classAmounts = items.map { it.classAmounts }.sumGradeAmounts(),
studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts()
)).reversed()
}
else -> items.filter { it.subject == subjectName } else -> items.filter { it.subject == subjectName }
}.mapToStatisticItems() }.mapPartialToStatisticItems()
}
)
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
shouldFetch = { it.isEmpty() || forceRefresh },
query = { local.getGradeSemesterStatistics(semester) },
fetch = { remote.getGradeSemesterStatistics(student, semester) },
saveFetchResult = { old, new ->
local.deleteGradeSemesterStatistics(old uniqueSubtract new)
local.saveGradeSemesterStatistics(new uniqueSubtract old)
},
mapResult = { items ->
val itemsWithAverage = items.map { item ->
item.copy().apply {
val denominator = item.amounts.sum()
average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount ->
(gradeValue + 1) * amount
}.sum().toDouble() / denominator).let {
"%.2f".format(Locale.FRANCE, it)
}
}
}
when (subjectName) {
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics(
studentId = semester.studentId,
semesterId = semester.semesterId,
subject = subjectName,
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
studentGrade = 0
).apply {
average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let {
"%.2f".format(Locale.FRANCE, it)
}
}).reversed()
else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems()
} }
) )
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource( fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
shouldFetch = { it.isEmpty() || forceRefresh }, shouldFetch = { it.isEmpty() || forceRefresh },
query = { local.getGradesPointsStatistics(semester) }, query = { local.getGradePointsStatistics(semester) },
fetch = { remote.getGradePointsStatistics(student, semester) }, fetch = { remote.getGradePointsStatistics(student, semester) },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
local.deleteGradesPointsStatistics(old uniqueSubtract new) local.deleteGradePointsStatistics(old uniqueSubtract new)
local.saveGradesPointsStatistics(new uniqueSubtract old) local.saveGradePointsStatistics(new uniqueSubtract old)
}, },
mapResult = { items -> mapResult = { items ->
when (subjectName) { when (subjectName) {
"Wszystkie" -> items "Wszystkie" -> items
else -> items.filter { it.subject == subjectName } else -> items.filter { it.subject == subjectName }
}.mapToStatisticsItem() }.mapPointsToStatisticsItems()
} }
) )
private fun List<GradeStatistics>.mapToStatisticItems() = groupBy { it.subject }.map { private fun List<List<Int>>.sumGradeAmounts(): List<Int> {
val result = mutableListOf(0, 0, 0, 0, 0, 0)
forEach {
it.forEachIndexed { grade, amount ->
result[grade] += amount
}
}
return result
}
private fun List<GradePartialStatistics>.mapPartialToStatisticItems() = filterNot { it.classAmounts.isEmpty() }.map {
GradeStatisticsItem( GradeStatisticsItem(
type = ViewType.PARTIAL, type = ViewType.PARTIAL,
partial = it.value average = it.classAverage,
.sortedByDescending { item -> item.grade } partial = it,
.filter { item -> item.amount != 0 }, points = null,
points = null semester = null
) )
} }
private fun List<GradePointsStatistics>.mapToStatisticsItem() = map { private fun List<GradeSemesterStatistics>.mapSemesterToStatisticItems() = filterNot { it.amounts.isEmpty() }.map {
GradeStatisticsItem(
type = ViewType.SEMESTER,
partial = null,
points = null,
average = "",
semester = it
)
}
private fun List<GradePointsStatistics>.mapPointsToStatisticsItems() = map {
GradeStatisticsItem( GradeStatisticsItem(
type = ViewType.POINTS, type = ViewType.POINTS,
partial = emptyList(), partial = null,
semester = null,
average = "",
points = it points = it
) )
} }

View File

@ -12,8 +12,8 @@ class GradeStatisticsWork @Inject constructor(
override suspend fun doWork(student: Student, semester: Semester) { override suspend fun doWork(student: Student, semester: Semester) {
with(gradeStatisticsRepository) { with(gradeStatisticsRepository) {
getGradesStatistics(student, semester, "Wszystkie", isSemester = true, forceRefresh = true).waitForResult() getGradesPartialStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult()
getGradesStatistics(student, semester, "Wszystkie", isSemester = false, forceRefresh = true).waitForResult() getGradesSemesterStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult()
getGradesPointsStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() getGradesPointsStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult()
} }
} }

View File

@ -17,8 +17,9 @@ import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry import com.github.mikephil.charting.data.PieEntry
import com.github.mikephil.charting.formatter.ValueFormatter import com.github.mikephil.charting.formatter.ValueFormatter
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.data.pojos.GradeStatisticsItem
import io.github.wulkanowy.databinding.ItemGradeStatisticsBarBinding import io.github.wulkanowy.databinding.ItemGradeStatisticsBarBinding
import io.github.wulkanowy.databinding.ItemGradeStatisticsPieBinding import io.github.wulkanowy.databinding.ItemGradeStatisticsPieBinding
@ -68,22 +69,32 @@ class GradeStatisticsAdapter @Inject constructor() :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
return when (viewType) { return when (viewType) {
ViewType.PARTIAL.id, ViewType.SEMESTER.id -> PieViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false)) ViewType.PARTIAL.id -> PartialViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false))
ViewType.POINTS.id -> BarViewHolder(ItemGradeStatisticsBarBinding.inflate(inflater, parent, false)) ViewType.SEMESTER.id -> SemesterViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false))
ViewType.POINTS.id -> PointsViewHolder(ItemGradeStatisticsBarBinding.inflate(inflater, parent, false))
else -> throw IllegalStateException() else -> throw IllegalStateException()
} }
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) { when (holder) {
is PieViewHolder -> bindPieChart(holder, items[position].partial) is PartialViewHolder -> bindPartialChart(holder, items[position].partial!!)
is BarViewHolder -> bindBarChart(holder, items[position].points!!) is SemesterViewHolder -> bindSemesterChart(holder, items[position].semester!!)
is PointsViewHolder -> bindBarChart(holder, items[position].points!!)
} }
} }
private fun bindPieChart(holder: PieViewHolder, partials: List<GradeStatistics>) { private fun bindPartialChart(holder: PartialViewHolder, partials: GradePartialStatistics) {
with(holder.binding.gradeStatisticsPieTitle) { bindPieChart(holder.binding, partials.subject, partials.classAverage, partials.classAmounts)
text = partials.firstOrNull()?.subject }
private fun bindSemesterChart(holder: SemesterViewHolder, semester: GradeSemesterStatistics) {
bindPieChart(holder.binding, semester.subject, semester.average, semester.amounts)
}
private fun bindPieChart(binding: ItemGradeStatisticsPieBinding, subject: String, average: String, amounts: List<Int>) {
with(binding.gradeStatisticsPieTitle) {
text = subject
visibility = if (items.size == 1 || !showAllSubjectsOnList) GONE else VISIBLE visibility = if (items.size == 1 || !showAllSubjectsOnList) GONE else VISIBLE
} }
@ -92,22 +103,23 @@ class GradeStatisticsAdapter @Inject constructor() :
else -> materialGradeColors else -> materialGradeColors
} }
val dataset = PieDataSet(partials.map { val dataset = PieDataSet(amounts.mapIndexed { grade, amount ->
PieEntry(it.amount.toFloat(), it.grade.toString()) PieEntry(amount.toFloat(), (grade + 1).toString())
}, "Legenda") }.reversed().filterNot { it.value == 0f }, "Legenda")
with(dataset) { with(dataset) {
valueTextSize = 12f valueTextSize = 12f
sliceSpace = 1f sliceSpace = 1f
valueTextColor = Color.WHITE valueTextColor = Color.WHITE
setColors(partials.map { val grades = amounts.mapIndexed { grade, amount -> (grade + 1) to amount }.filterNot { it.second == 0 }
gradeColors.single { color -> color.first == it.grade }.second setColors(grades.reversed().map { (grade, _) ->
}.toIntArray(), holder.binding.root.context) gradeColors.single { color -> color.first == grade }.second
}.toIntArray(), binding.root.context)
} }
with(holder.binding.gradeStatisticsPie) { with(binding.gradeStatisticsPie) {
setTouchEnabled(false) setTouchEnabled(false)
if (partials.size == 1) animateXY(1000, 1000) if (amounts.size == 1) animateXY(1000, 1000)
data = PieData(dataset).apply { data = PieData(dataset).apply {
setValueFormatter(object : ValueFormatter() { setValueFormatter(object : ValueFormatter() {
override fun getPieLabel(value: Float, pieEntry: PieEntry): String { override fun getPieLabel(value: Float, pieEntry: PieEntry): String {
@ -128,8 +140,9 @@ class GradeStatisticsAdapter @Inject constructor() :
minAngleForSlices = 25f minAngleForSlices = 25f
description.isEnabled = false description.isEnabled = false
centerText = partials.fold(0) { acc, it -> acc + it.amount } centerText = amounts.fold(0) { acc, it -> acc + it }
.let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } +
("\n\nŚrednia: $average").takeIf { average.isNotBlank() }.orEmpty()
setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground)) setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground))
setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary)) setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary))
@ -137,7 +150,7 @@ class GradeStatisticsAdapter @Inject constructor() :
} }
} }
private fun bindBarChart(holder: BarViewHolder, points: GradePointsStatistics) { private fun bindBarChart(holder: PointsViewHolder, points: GradePointsStatistics) {
with(holder.binding.gradeStatisticsBarTitle) { with(holder.binding.gradeStatisticsBarTitle) {
text = points.subject text = points.subject
visibility = if (items.size == 1) GONE else VISIBLE visibility = if (items.size == 1) GONE else VISIBLE
@ -200,9 +213,12 @@ class GradeStatisticsAdapter @Inject constructor() :
} }
} }
private class PieViewHolder(val binding: ItemGradeStatisticsPieBinding) : private class PartialViewHolder(val binding: ItemGradeStatisticsPieBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
private class BarViewHolder(val binding: ItemGradeStatisticsBarBinding) : private class SemesterViewHolder(val binding: ItemGradeStatisticsPieBinding) :
RecyclerView.ViewHolder(binding.root)
private class PointsViewHolder(val binding: ItemGradeStatisticsBarBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
} }

View File

@ -153,8 +153,8 @@ class GradeStatisticsPresenter @Inject constructor(
with(gradeStatisticsRepository) { with(gradeStatisticsRepository) {
when (type) { when (type) {
ViewType.SEMESTER -> getGradesStatistics(student, semester, currentSubjectName, true, forceRefresh) ViewType.PARTIAL -> getGradesPartialStatistics(student, semester, currentSubjectName, forceRefresh)
ViewType.PARTIAL -> getGradesStatistics(student, semester, currentSubjectName, false, forceRefresh) ViewType.SEMESTER -> getGradesSemesterStatistics(student, semester, currentSubjectName, forceRefresh)
ViewType.POINTS -> getGradesPointsStatistics(student, semester, currentSubjectName, forceRefresh) ViewType.POINTS -> getGradesPointsStatistics(student, semester, currentSubjectName, forceRefresh)
} }
} }
@ -164,8 +164,15 @@ class GradeStatisticsPresenter @Inject constructor(
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading grade stats result: Success") Timber.i("Loading grade stats result: Success")
view?.run { view?.run {
showEmpty(it.data!!.isEmpty() || it.data.first().partial.isEmpty()) val isNoContent = it.data!!.isEmpty() || when (type) {
showContent(it.data.isNotEmpty() && it.data.first().partial.isNotEmpty()) ViewType.SEMESTER -> it.data.firstOrNull()?.semester?.amounts.orEmpty().sum() == 0
ViewType.PARTIAL -> it.data.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0
ViewType.POINTS -> it.data.firstOrNull()?.points?.let { points ->
points.student == .0 && points.others == .0
} ?: false
}
showEmpty(isNoContent)
showContent(!isNoContent)
showErrorView(false) showErrorView(false)
updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList) updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)

View File

@ -4,7 +4,8 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.getStudentEntity
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.GradePointsStatistics import io.github.wulkanowy.sdk.pojo.GradePointsStatistics
import io.github.wulkanowy.sdk.pojo.GradeStatistics import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.coEvery import io.mockk.coEvery
@ -35,8 +36,8 @@ class GradeStatisticsRemoteTest {
@Test @Test
fun getGradeStatisticsTest() { fun getGradeStatisticsTest() {
coEvery { mockSdk.getGradesPartialStatistics(1) } returns listOf( coEvery { mockSdk.getGradesPartialStatistics(1) } returns listOf(
getGradeStatistics("Fizyka"), getGradeStatisticsPartialSubject("Fizyka"),
getGradeStatistics("Matematyka") getGradeStatisticsPartialSubject("Matematyka")
) )
every { semesterMock.studentId } returns 1 every { semesterMock.studentId } returns 1
@ -45,7 +46,7 @@ class GradeStatisticsRemoteTest {
every { semesterMock.semesterId } returns 1 every { semesterMock.semesterId } returns 1
every { mockSdk.switchDiary(any(), any()) } returns mockSdk every { mockSdk.switchDiary(any(), any()) } returns mockSdk
val stats = runBlocking { GradeStatisticsRemote(mockSdk).getGradeStatistics(student, semesterMock, false) } val stats = runBlocking { GradeStatisticsRemote(mockSdk).getGradePartialStatistics(student, semesterMock) }
assertEquals(2, stats.size) assertEquals(2, stats.size)
} }
@ -66,19 +67,24 @@ class GradeStatisticsRemoteTest {
assertEquals(2, stats.size) assertEquals(2, stats.size)
} }
private fun getGradeStatistics(subjectName: String): GradeStatistics { private fun getGradeStatisticsPartialSubject(subjectName: String): GradeStatisticsSubject {
return GradeStatistics( return GradeStatisticsSubject(
subject = subjectName, subject = subjectName,
gradeValue = 5, studentAverage = "",
amount = 10, classAverage = "",
grade = "", classItems = listOf(
semesterId = 1 GradeStatisticsItem(
subject = subjectName,
grade = 0,
amount = 0
)
),
studentItems = listOf()
) )
} }
private fun getGradePointsStatistics(subjectName: String): GradePointsStatistics { private fun getGradePointsStatistics(subjectName: String): GradePointsStatistics {
return GradePointsStatistics( return GradePointsStatistics(
semesterId = 1,
subject = subjectName, subject = subjectName,
student = 0.80, student = 0.80,
others = 0.40 others = 0.40