1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2025-01-31 12: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\""
}
debug {
minifyEnabled false
shrinkResources false
resValue "string", "app_name", "Wulkanowy DEV"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"

View File

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

View File

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

View File

@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
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.data.*
import com.github.mikephil.charting.formatter.ValueFormatter
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
@ -136,20 +131,50 @@ class GradeStatisticsAdapter @Inject constructor() :
binding: ItemGradeStatisticsPieBinding,
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(
binding: ItemGradeStatisticsPieBinding,
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(
binding: ItemGradeStatisticsPieBinding,
subject: String,
average: String,
studentValue: String?,
amounts: List<Int>
) {
with(binding.gradeStatisticsPieTitle) {
@ -208,13 +233,13 @@ class GradeStatisticsAdapter @Inject constructor() :
val numberOfGradesString = amounts.fold(0) { acc, it -> acc + it }
.let { resources.getQuantityString(R.plurals.grade_number_item, it, it) }
val averageString =
binding.root.context.getString(R.string.grade_statistics_average, average)
binding.root.context.getString(R.string.grade_statistics_class_average, average)
minAngleForSlices = 25f
description.isEnabled = false
centerText =
numberOfGradesString + ("\n\n" + averageString).takeIf { average.isNotBlank() }
.orEmpty()
.orEmpty() + studentValue?.let { "\n$it" }.orEmpty()
setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground))
setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary))

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Semestr</string>
<string name="grade_statistics_points">Body</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_student">Žák</string>
<plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Semester</string>
<string name="grade_statistics_points">Punkte</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_student">Schüler</string>
<plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Semestralne</string>
<string name="grade_statistics_points">Punkty</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_student">Uczeń</string>
<plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">За семестр</string>
<string name="grade_statistics_points">Баллы</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_student">Студент</string>
<plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Semester</string>
<string name="grade_statistics_points">Body</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_student">Žiák</string>
<plurals name="grade_number_item">

View File

@ -105,7 +105,7 @@
<string name="grade_statistics_semester">Семестрові</string>
<string name="grade_statistics_points">Бали</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_student">Учень</string>
<plurals name="grade_number_item">

View File

@ -115,7 +115,9 @@
<string name="grade_statistics_semester">Semester</string>
<string name="grade_statistics_points">Points</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_student">Student</string>
<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.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.toFirstResult
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.*
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK
import io.mockk.just
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
@ -48,22 +43,27 @@ class GradeStatisticsRepositoryTest {
private lateinit var gradeStatisticsRepository: GradeStatisticsRepository
private val remotePartialList = listOf(
getGradeStatisticsPartialSubject("Fizyka"),
getGradeStatisticsPartialSubject("Matematyka")
)
@Before
fun setUp() {
MockKAnnotations.init(this)
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
fun `force refresh without difference`() {
// prepare
val remotePartialList = listOf(
getGradeStatisticsPartialSubject("Fizyka"),
getGradeStatisticsPartialSubject("Matematyka")
)
coEvery { sdk.getGradesPartialStatistics(1) } returns remotePartialList
coEvery { gradePartialStatisticsDb.loadAll(1, 1) } returnsMany listOf(
flowOf(remotePartialList.mapToEntities(semester)),
@ -73,21 +73,74 @@ class GradeStatisticsRepositoryTest {
coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs
// 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
assertEquals(null, res.error)
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 { gradePartialStatisticsDb.loadAll(1, 1) }
coVerify { gradePartialStatisticsDb.insertAll(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,
studentAverage = "",
classAverage = "",
studentAverage = studentAverage,
classAverage = classAverage,
classItems = listOf(
GradeStatisticsItem(
subject = subjectName,