1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2025-01-31 14:48:20 +01:00

Add information about student in grade statistics pie chart (#1749)

Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
This commit is contained in:
Michael 2022-02-15 13:08:44 +01:00 committed by GitHub
parent d3bf5c3e0a
commit aff0fb3a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 153 additions and 59 deletions

View File

@ -73,6 +73,8 @@ android {
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
} }
debug { debug {
minifyEnabled false
shrinkResources false
resValue "string", "app_name", "Wulkanowy DEV" resValue "string", "app_name", "Wulkanowy DEV"
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
versionNameSuffix "-dev" versionNameSuffix "-dev"

View File

@ -24,5 +24,8 @@ data class GradeSemesterStatistics(
var id: Long = 0 var id: Long = 0
@Transient @Transient
var average: String = "" var classAverage: String = ""
@Transient
var studentAverage: String = ""
} }

View File

@ -63,20 +63,16 @@ class GradeStatisticsRepository @Inject constructor(
mapResult = { items -> mapResult = { items ->
when (subjectName) { when (subjectName) {
"Wszystkie" -> { "Wszystkie" -> {
val numerator = items.map { val summaryItem = GradePartialStatistics(
it.classAverage.replace(",", ".").toDoubleOrNull() ?: .0
}.filterNot { it == .0 }
(items.reversed() + GradePartialStatistics(
studentId = semester.studentId, studentId = semester.studentId,
semesterId = semester.semesterId, semesterId = semester.semesterId,
subject = subjectName, subject = subjectName,
classAverage = if (numerator.isEmpty()) "" else numerator.average().let { classAverage = items.map { it.classAverage }.getSummaryAverage(),
"%.2f".format(Locale.FRANCE, it) studentAverage = items.map { it.studentAverage }.getSummaryAverage(),
},
studentAverage = "",
classAmounts = items.map { it.classAmounts }.sumGradeAmounts(), classAmounts = items.map { it.classAmounts }.sumGradeAmounts(),
studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts() studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts()
)).reversed() )
listOf(summaryItem) + items
} }
else -> items.filter { it.subject == subjectName } else -> items.filter { it.subject == subjectName }
}.mapPartialToStatisticItems() }.mapPartialToStatisticItems()
@ -112,29 +108,29 @@ class GradeStatisticsRepository @Inject constructor(
val itemsWithAverage = items.map { item -> val itemsWithAverage = items.map { item ->
item.copy().apply { item.copy().apply {
val denominator = item.amounts.sum() val denominator = item.amounts.sum()
average = if (denominator == 0) "" else { classAverage = if (denominator == 0) "" else {
(item.amounts.mapIndexed { gradeValue, amount -> (item.amounts.mapIndexed { gradeValue, amount ->
(gradeValue + 1) * amount (gradeValue + 1) * amount
}.sum().toDouble() / denominator).let { }.sum().toDouble() / denominator).asAverageString()
"%.2f".format(Locale.FRANCE, it)
}
} }
} }
} }
when (subjectName) { when (subjectName) {
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics( "Wszystkie" -> {
studentId = semester.studentId, val summaryItem = GradeSemesterStatistics(
semesterId = semester.semesterId, studentId = semester.studentId,
subject = subjectName, semesterId = semester.semesterId,
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(), subject = subjectName,
studentGrade = 0 amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
).apply { studentGrade = 0,
average = itemsWithAverage.mapNotNull { ).apply {
it.average.replace(",", ".").toDoubleOrNull() classAverage = itemsWithAverage.map { it.classAverage }.getSummaryAverage()
}.average().let { studentAverage = items
"%.2f".format(Locale.FRANCE, it) .mapNotNull { summary -> summary.studentGrade.takeIf { it != 0 } }
.average().asAverageString()
} }
}).reversed() listOf(summaryItem) + itemsWithAverage
}
else -> itemsWithAverage.filter { it.subject == subjectName } else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems() }.mapSemesterToStatisticItems()
} }
@ -171,6 +167,19 @@ class GradeStatisticsRepository @Inject constructor(
} }
) )
private fun List<String>.getSummaryAverage(): String {
val averages = mapNotNull {
it.replace(",", ".").toDoubleOrNull()
}
return averages.average()
.asAverageString()
.takeIf { averages.isNotEmpty() }
.orEmpty()
}
private fun Double.asAverageString(): String = "%.2f".format(Locale.FRANCE, this)
private fun List<List<Int>>.sumGradeAmounts(): List<Int> { private fun List<List<Int>>.sumGradeAmounts(): List<Int> {
val result = mutableListOf(0, 0, 0, 0, 0, 0) val result = mutableListOf(0, 0, 0, 0, 0, 0)
forEach { forEach {

View File

@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.mikephil.charting.components.Legend import com.github.mikephil.charting.components.Legend
import com.github.mikephil.charting.components.LegendEntry import com.github.mikephil.charting.components.LegendEntry
import com.github.mikephil.charting.data.BarData import com.github.mikephil.charting.data.*
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 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.GradePartialStatistics
@ -136,20 +131,50 @@ class GradeStatisticsAdapter @Inject constructor() :
binding: ItemGradeStatisticsPieBinding, binding: ItemGradeStatisticsPieBinding,
partials: GradePartialStatistics partials: GradePartialStatistics
) { ) {
bindPieChart(binding, partials.subject, partials.classAverage, partials.classAmounts) val studentAverage = partials.studentAverage.takeIf { it.isNotEmpty() }?.let {
binding.root.context.getString(R.string.grade_statistics_student_average, it)
}
bindPieChart(
binding = binding,
subject = partials.subject,
average = partials.classAverage,
studentValue = studentAverage,
amounts = partials.classAmounts
)
} }
private fun bindSemesterChart( private fun bindSemesterChart(
binding: ItemGradeStatisticsPieBinding, binding: ItemGradeStatisticsPieBinding,
semester: GradeSemesterStatistics semester: GradeSemesterStatistics
) { ) {
bindPieChart(binding, semester.subject, semester.average, semester.amounts) val studentAverage = semester.studentAverage.takeIf { it.isNotBlank() }
val studentGrade = semester.studentGrade.takeIf { it != 0 }
val studentValue = when {
studentAverage != null -> binding.root.context.getString(
R.string.grade_statistics_student_average,
studentAverage
)
studentGrade != null -> binding.root.context.getString(
R.string.grade_statistics_student_grade,
studentGrade.toString()
)
else -> null
}
bindPieChart(
binding = binding,
subject = semester.subject,
average = semester.classAverage,
studentValue = studentValue,
amounts = semester.amounts
)
} }
private fun bindPieChart( private fun bindPieChart(
binding: ItemGradeStatisticsPieBinding, binding: ItemGradeStatisticsPieBinding,
subject: String, subject: String,
average: String, average: String,
studentValue: String?,
amounts: List<Int> amounts: List<Int>
) { ) {
with(binding.gradeStatisticsPieTitle) { with(binding.gradeStatisticsPieTitle) {
@ -208,13 +233,13 @@ class GradeStatisticsAdapter @Inject constructor() :
val numberOfGradesString = amounts.fold(0) { acc, it -> acc + it } val numberOfGradesString = 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) }
val averageString = val averageString =
binding.root.context.getString(R.string.grade_statistics_average, average) binding.root.context.getString(R.string.grade_statistics_class_average, average)
minAngleForSlices = 25f minAngleForSlices = 25f
description.isEnabled = false description.isEnabled = false
centerText = centerText =
numberOfGradesString + ("\n\n" + averageString).takeIf { average.isNotBlank() } numberOfGradesString + ("\n\n" + averageString).takeIf { average.isNotBlank() }
.orEmpty() .orEmpty() + studentValue?.let { "\n$it" }.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))

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Semestr</string> <string name="grade_statistics_semester">Semestr</string>
<string name="grade_statistics_points">Body</string> <string name="grade_statistics_points">Body</string>
<string name="grade_statistics_legend">Vysvětlivky</string> <string name="grade_statistics_legend">Vysvětlivky</string>
<string name="grade_statistics_average">Průměr: %1$s</string> <string name="grade_statistics_class_average">Průměr: %1$s</string>
<string name="grade_statistics_average_class">Třída</string> <string name="grade_statistics_average_class">Třída</string>
<string name="grade_statistics_average_student">Žák</string> <string name="grade_statistics_average_student">Žák</string>
<plurals name="grade_number_item"> <plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Semester</string> <string name="grade_statistics_semester">Semester</string>
<string name="grade_statistics_points">Punkte</string> <string name="grade_statistics_points">Punkte</string>
<string name="grade_statistics_legend">Legende</string> <string name="grade_statistics_legend">Legende</string>
<string name="grade_statistics_average">Durchschnitt: %1$s</string> <string name="grade_statistics_class_average">Durchschnitt: %1$s</string>
<string name="grade_statistics_average_class">Klasse</string> <string name="grade_statistics_average_class">Klasse</string>
<string name="grade_statistics_average_student">Schüler</string> <string name="grade_statistics_average_student">Schüler</string>
<plurals name="grade_number_item"> <plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Semestralne</string> <string name="grade_statistics_semester">Semestralne</string>
<string name="grade_statistics_points">Punkty</string> <string name="grade_statistics_points">Punkty</string>
<string name="grade_statistics_legend">Legenda</string> <string name="grade_statistics_legend">Legenda</string>
<string name="grade_statistics_average">Średnia: %1$s</string> <string name="grade_statistics_class_average">Średnia: %1$s</string>
<string name="grade_statistics_average_class">Klasa</string> <string name="grade_statistics_average_class">Klasa</string>
<string name="grade_statistics_average_student">Uczeń</string> <string name="grade_statistics_average_student">Uczeń</string>
<plurals name="grade_number_item"> <plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">За семестр</string> <string name="grade_statistics_semester">За семестр</string>
<string name="grade_statistics_points">Баллы</string> <string name="grade_statistics_points">Баллы</string>
<string name="grade_statistics_legend">Легенда</string> <string name="grade_statistics_legend">Легенда</string>
<string name="grade_statistics_average">Средняя: %1$s</string> <string name="grade_statistics_class_average">Средняя: %1$s</string>
<string name="grade_statistics_average_class">Класс</string> <string name="grade_statistics_average_class">Класс</string>
<string name="grade_statistics_average_student">Студент</string> <string name="grade_statistics_average_student">Студент</string>
<plurals name="grade_number_item"> <plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Semester</string> <string name="grade_statistics_semester">Semester</string>
<string name="grade_statistics_points">Body</string> <string name="grade_statistics_points">Body</string>
<string name="grade_statistics_legend">Vysvetlivky</string> <string name="grade_statistics_legend">Vysvetlivky</string>
<string name="grade_statistics_average">Priemer: %1$s</string> <string name="grade_statistics_class_average">Priemer: %1$s</string>
<string name="grade_statistics_average_class">Trieda</string> <string name="grade_statistics_average_class">Trieda</string>
<string name="grade_statistics_average_student">Žiák</string> <string name="grade_statistics_average_student">Žiák</string>
<plurals name="grade_number_item"> <plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Семестрові</string> <string name="grade_statistics_semester">Семестрові</string>
<string name="grade_statistics_points">Бали</string> <string name="grade_statistics_points">Бали</string>
<string name="grade_statistics_legend">Умовні позначення</string> <string name="grade_statistics_legend">Умовні позначення</string>
<string name="grade_statistics_average">Середня оцінка: %1$s</string> <string name="grade_statistics_class_average">Середня оцінка: %1$s</string>
<string name="grade_statistics_average_class">Клас</string> <string name="grade_statistics_average_class">Клас</string>
<string name="grade_statistics_average_student">Учень</string> <string name="grade_statistics_average_student">Учень</string>
<plurals name="grade_number_item"> <plurals name="grade_number_item">

View File

@ -115,7 +115,9 @@
<string name="grade_statistics_semester">Semester</string> <string name="grade_statistics_semester">Semester</string>
<string name="grade_statistics_points">Points</string> <string name="grade_statistics_points">Points</string>
<string name="grade_statistics_legend">Legend</string> <string name="grade_statistics_legend">Legend</string>
<string name="grade_statistics_average">Average: %1$s</string> <string name="grade_statistics_class_average">Class average: %1$s</string>
<string name="grade_statistics_student_average">Your average: %1$s</string>
<string name="grade_statistics_student_grade">Your grade: %1$s</string>
<string name="grade_statistics_average_class">Class</string> <string name="grade_statistics_average_class">Class</string>
<string name="grade_statistics_average_student">Student</string> <string name="grade_statistics_average_student">Student</string>
<plurals name="grade_number_item"> <plurals name="grade_number_item">

View File

@ -11,14 +11,9 @@ import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem
import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.toFirstResult import io.github.wulkanowy.utils.toFirstResult
import io.mockk.MockKAnnotations import io.mockk.*
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK import io.mockk.impl.annotations.SpyK
import io.mockk.just
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -48,22 +43,27 @@ class GradeStatisticsRepositoryTest {
private lateinit var gradeStatisticsRepository: GradeStatisticsRepository private lateinit var gradeStatisticsRepository: GradeStatisticsRepository
private val remotePartialList = listOf(
getGradeStatisticsPartialSubject("Fizyka"),
getGradeStatisticsPartialSubject("Matematyka")
)
@Before @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
every { refreshHelper.shouldBeRefreshed(any()) } returns false every { refreshHelper.shouldBeRefreshed(any()) } returns false
gradeStatisticsRepository = GradeStatisticsRepository(gradePartialStatisticsDb, gradePointsStatisticsDb, gradeSemesterStatisticsDb, sdk, refreshHelper) gradeStatisticsRepository = GradeStatisticsRepository(
gradePartialStatisticsDb = gradePartialStatisticsDb,
gradePointsStatisticsDb = gradePointsStatisticsDb,
gradeSemesterStatisticsDb = gradeSemesterStatisticsDb,
sdk = sdk,
refreshHelper = refreshHelper,
)
} }
@Test @Test
fun `force refresh without difference`() { fun `force refresh without difference`() {
// prepare // prepare
val remotePartialList = listOf(
getGradeStatisticsPartialSubject("Fizyka"),
getGradeStatisticsPartialSubject("Matematyka")
)
coEvery { sdk.getGradesPartialStatistics(1) } returns remotePartialList coEvery { sdk.getGradesPartialStatistics(1) } returns remotePartialList
coEvery { gradePartialStatisticsDb.loadAll(1, 1) } returnsMany listOf( coEvery { gradePartialStatisticsDb.loadAll(1, 1) } returnsMany listOf(
flowOf(remotePartialList.mapToEntities(semester)), flowOf(remotePartialList.mapToEntities(semester)),
@ -73,21 +73,74 @@ class GradeStatisticsRepositoryTest {
coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { gradeStatisticsRepository.getGradesPartialStatistics(student, semester, "Wszystkie", true).toFirstResult() } val res = runBlocking {
gradeStatisticsRepository.getGradesPartialStatistics(
student = student,
semester = semester,
subjectName = "Wszystkie",
forceRefresh = true,
).toFirstResult()
}
val items = res.data.orEmpty()
// verify // verify
assertEquals(null, res.error) assertEquals(null, res.error)
assertEquals(2 + 1, res.data?.size) assertEquals(2 + 1, res.data?.size)
assertEquals("", items[0].partial?.studentAverage)
assertEquals("", items[1].partial?.studentAverage)
assertEquals("", items[2].partial?.studentAverage)
coVerify { sdk.getGradesPartialStatistics(1) } coVerify { sdk.getGradesPartialStatistics(1) }
coVerify { gradePartialStatisticsDb.loadAll(1, 1) } coVerify { gradePartialStatisticsDb.loadAll(1, 1) }
coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) } coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) }
coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) } coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) }
} }
private fun getGradeStatisticsPartialSubject(subjectName: String) = GradeStatisticsSubject( @Test
fun `force refresh without difference with filled up items`() {
// prepare
val remotePartialList = listOf(
getGradeStatisticsPartialSubject("Fizyka", "1.0"),
getGradeStatisticsPartialSubject("Matematyka", "5.0")
)
coEvery { sdk.getGradesPartialStatistics(1) } returns remotePartialList
coEvery { gradePartialStatisticsDb.loadAll(1, 1) } returnsMany listOf(
flowOf(remotePartialList.mapToEntities(semester)),
flowOf(remotePartialList.mapToEntities(semester))
)
coEvery { gradePartialStatisticsDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs
// execute
val res = runBlocking {
gradeStatisticsRepository.getGradesPartialStatistics(
student = student,
semester = semester,
subjectName = "Wszystkie",
forceRefresh = true,
).toFirstResult()
}
val items = res.data.orEmpty()
// verify
assertEquals(null, res.error)
assertEquals(2 + 1, res.data?.size)
assertEquals("3,00", items[0].partial?.studentAverage)
assertEquals("1.0", items[1].partial?.studentAverage)
assertEquals("5.0", items[2].partial?.studentAverage)
coVerify { sdk.getGradesPartialStatistics(1) }
coVerify { gradePartialStatisticsDb.loadAll(1, 1) }
coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) }
coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) }
}
private fun getGradeStatisticsPartialSubject(
subjectName: String,
studentAverage: String = "",
classAverage: String = "",
) = GradeStatisticsSubject(
subject = subjectName, subject = subjectName,
studentAverage = "", studentAverage = studentAverage,
classAverage = "", classAverage = classAverage,
classItems = listOf( classItems = listOf(
GradeStatisticsItem( GradeStatisticsItem(
subject = subjectName, subject = subjectName,