From aff0fb3a6028bafeac7c91fb22bf9a3872f13840 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Tue, 15 Feb 2022 13:08:44 +0100 Subject: [PATCH] Add information about student in grade statistics pie chart (#1749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- app/build.gradle | 2 + .../db/entities/GradeSemesterStatistics.kt | 5 +- .../repositories/GradeStatisticsRepository.kt | 59 +++++++------ .../statistics/GradeStatisticsAdapter.kt | 45 +++++++--- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values/strings.xml | 4 +- .../GradeStatisticsRepositoryTest.kt | 85 +++++++++++++++---- 12 files changed, 153 insertions(+), 59 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e9029a23b..609a54eb9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt index e747271ce..9e08b86bc 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt @@ -24,5 +24,8 @@ data class GradeSemesterStatistics( var id: Long = 0 @Transient - var average: String = "" + var classAverage: String = "" + + @Transient + var studentAverage: String = "" } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt index 356c203d0..4d26c3126 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt @@ -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.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>.sumGradeAmounts(): List { val result = mutableListOf(0, 0, 0, 0, 0, 0) forEach { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt index 6be2d969f..fd0ac5471 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt @@ -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 ) { 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)) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index b56617ed7..e7c2da332 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -105,7 +105,7 @@ Semestr Body Vysvětlivky - Průměr: %1$s + Průměr: %1$s Třída Žák diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5b2fee80d..68c371d51 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -105,7 +105,7 @@ Semester Punkte Legende - Durchschnitt: %1$s + Durchschnitt: %1$s Klasse Schüler diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 208e6f3ea..da127fe2d 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -105,7 +105,7 @@ Semestralne Punkty Legenda - Średnia: %1$s + Średnia: %1$s Klasa Uczeń diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2ae1aad7f..e6e9834f5 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -105,7 +105,7 @@ За семестр Баллы Легенда - Средняя: %1$s + Средняя: %1$s Класс Студент diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 30365fe56..ba701eafd 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -105,7 +105,7 @@ Semester Body Vysvetlivky - Priemer: %1$s + Priemer: %1$s Trieda Žiák diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 9dedb2060..7bcffe01b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -105,7 +105,7 @@ Семестрові Бали Умовні позначення - Середня оцінка: %1$s + Середня оцінка: %1$s Клас Учень diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9c63073e2..d565d65cc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -115,7 +115,9 @@ Semester Points Legend - Average: %1$s + Class average: %1$s + Your average: %1$s + Your grade: %1$s Class Student diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt index cce3794de..6221b6989 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt @@ -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,