From be057dd63c507a89a053ef0cb02c3cd4bb927931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 29 Feb 2020 02:19:48 +0100 Subject: [PATCH] Show list of charts in grade statistics (#689) --- .../GradeStatisticsLocalTest.kt | 6 +- .../data/db/dao/GradePointsStatisticsDao.kt | 2 +- .../data/pojos/GradeStatisticsItem.kt | 14 ++ .../gradestatistics/GradeStatisticsLocal.kt | 15 +- .../GradeStatisticsRepository.kt | 45 +++- .../statistics/GradeStatisticsAdapter.kt | 209 ++++++++++++++++++ .../statistics/GradeStatisticsFragment.kt | 178 ++------------- .../statistics/GradeStatisticsPresenter.kt | 80 ++----- .../grade/statistics/GradeStatisticsView.kt | 15 +- .../github/wulkanowy/utils/NumberExtension.kt | 7 - .../res/layout/fragment_grade_statistics.xml | 21 +- .../res/layout/item_grade_statistics_bar.xml | 27 +++ .../res/layout/item_grade_statistics_pie.xml | 27 +++ 13 files changed, 372 insertions(+), 274 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/NumberExtension.kt create mode 100644 app/src/main/res/layout/item_grade_statistics_bar.xml create mode 100644 app/src/main/res/layout/item_grade_statistics_pie.xml diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt index 0d27b9bb7..bd3635fea 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt @@ -54,8 +54,10 @@ class GradeStatisticsLocalTest { )) val stats = gradeStatisticsLocal.getGradesStatistics(getSemester(), false, "Wszystkie").blockingGet() - assertEquals(1, stats.size) + assertEquals(3, stats.size) assertEquals(stats[0].subject, "Wszystkie") + assertEquals(stats[1].subject, "Matematyka") + assertEquals(stats[2].subject, "Chemia") } @Test @@ -67,7 +69,7 @@ class GradeStatisticsLocalTest { )) val stats = gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Matematyka").blockingGet() - with(stats) { + with(stats[0]) { assertEquals(subject, "Matematyka") assertEquals(others, 5.0) assertEquals(student, 5.0) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt index 376092865..da1828666 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePointsStatisticsDao.kt @@ -11,7 +11,7 @@ import javax.inject.Singleton interface GradePointsStatisticsDao : BaseDao { @Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName") - fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Maybe + fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Maybe> @Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId") fun loadAll(semesterId: Int, studentId: Int): Maybe> diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt new file mode 100644 index 000000000..34b62abdb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.data.pojos + +import io.github.wulkanowy.data.db.entities.GradePointsStatistics +import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.ui.modules.grade.statistics.ViewType + +data class GradeStatisticsItem( + + val type: ViewType, + + val partial: List, + + val points: GradePointsStatistics? +) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt index 279e2959d..7994bd758 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt @@ -5,7 +5,6 @@ import io.github.wulkanowy.data.db.dao.GradeStatisticsDao import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.utils.roundToDecimalPlaces import io.reactivex.Maybe import javax.inject.Inject import javax.inject.Singleton @@ -30,23 +29,17 @@ class GradeStatisticsLocal @Inject constructor( list.groupBy { it.grade }.map { GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, it.value.fold(0) { acc, e -> acc + e.amount }, false) - } + } + list } else -> gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester) }.filter { it.isNotEmpty() } } - fun getGradesPointsStatistics(semester: Semester, subjectName: String): Maybe { + fun getGradesPointsStatistics(semester: Semester, subjectName: String): Maybe> { return when (subjectName) { - "Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).flatMap { list -> - if (list.isEmpty()) return@flatMap Maybe.empty() - Maybe.just(GradePointsStatistics(semester.studentId, semester.semesterId, subjectName, - (list.fold(.0) { acc, e -> acc + e.others } / list.size).roundToDecimalPlaces(2), - (list.fold(.0) { acc, e -> acc + e.student } / list.size).roundToDecimalPlaces(2) - )) - } + "Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) else -> gradePointsStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName) - } + }.filter { it.isNotEmpty() } } fun saveGradesStatistics(gradesStatistics: List) { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt index 9335d1d21..e82e9db4d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt @@ -5,8 +5,9 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter 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.pojos.GradeStatisticsItem +import io.github.wulkanowy.ui.modules.grade.statistics.ViewType import io.github.wulkanowy.utils.uniqueSubtract -import io.reactivex.Maybe import io.reactivex.Single import java.net.UnknownHostException import javax.inject.Inject @@ -19,8 +20,8 @@ class GradeStatisticsRepository @Inject constructor( private val remote: GradeStatisticsRemote ) { - fun getGradesStatistics(semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false): Single> { - return local.getGradesStatistics(semester, isSemester, subjectName).filter { !forceRefresh } + fun getGradesStatistics(semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false): Single> { + return local.getGradesStatistics(semester, isSemester, subjectName).map { it.mapToStatisticItems() }.filter { !forceRefresh } .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) .flatMap { if (it) remote.getGradeStatistics(semester, isSemester) @@ -31,21 +32,43 @@ class GradeStatisticsRepository @Inject constructor( local.deleteGradesStatistics(old.uniqueSubtract(new)) local.saveGradesStatistics(new.uniqueSubtract(old)) } - }.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).toSingle(emptyList()) }) + }.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).map { it.mapToStatisticItems() }.toSingle(emptyList()) }) } - fun getGradesPointsStatistics(semester: Semester, subjectName: String, forceRefresh: Boolean): Maybe { - return local.getGradesPointsStatistics(semester, subjectName).filter { !forceRefresh } + fun getGradesPointsStatistics(semester: Semester, subjectName: String, forceRefresh: Boolean): Single> { + return local.getGradesPointsStatistics(semester, subjectName).map { it.mapToStatisticsItem() }.filter { !forceRefresh } .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMapMaybe { - if (it) remote.getGradePointsStatistics(semester).toMaybe() - else Maybe.error(UnknownHostException()) + .flatMap { + if (it) remote.getGradePointsStatistics(semester) + else Single.error(UnknownHostException()) }.flatMap { new -> - local.getGradesPointsStatistics(semester).defaultIfEmpty(emptyList()) + local.getGradesPointsStatistics(semester).toSingle(emptyList()) .doOnSuccess { old -> local.deleteGradesPointsStatistics(old.uniqueSubtract(new)) local.saveGradesPointsStatistics(new.uniqueSubtract(old)) } - }.flatMap { local.getGradesPointsStatistics(semester, subjectName) }) + }.flatMap { local.getGradesPointsStatistics(semester, subjectName).map { it.mapToStatisticsItem() }.toSingle(emptyList()) }) + } + + private fun List.mapToStatisticItems(): List { + return groupBy { it.subject }.map { + GradeStatisticsItem( + type = ViewType.PARTIAL, + partial = it.value + .sortedByDescending { item -> item.grade } + .filter { item -> item.amount != 0 }, + points = null + ) + } + } + + private fun List.mapToStatisticsItem(): List { + return map { + GradeStatisticsItem( + type = ViewType.POINTS, + partial = emptyList(), + points = it + ) + } } } 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 new file mode 100644 index 000000000..a7b7b6534 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt @@ -0,0 +1,209 @@ +package io.github.wulkanowy.ui.modules.grade.statistics + +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +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.formatter.ValueFormatter +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.GradePointsStatistics +import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.pojos.GradeStatisticsItem +import io.github.wulkanowy.utils.getThemeAttrColor +import kotlinx.android.synthetic.main.item_grade_statistics_bar.view.* +import kotlinx.android.synthetic.main.item_grade_statistics_pie.view.* +import javax.inject.Inject + +class GradeStatisticsAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + var theme: String = "vulcan" + + private val vulcanGradeColors = listOf( + 6 to R.color.grade_vulcan_six, + 5 to R.color.grade_vulcan_five, + 4 to R.color.grade_vulcan_four, + 3 to R.color.grade_vulcan_three, + 2 to R.color.grade_vulcan_two, + 1 to R.color.grade_vulcan_one + ) + + private val materialGradeColors = listOf( + 6 to R.color.grade_material_six, + 5 to R.color.grade_material_five, + 4 to R.color.grade_material_four, + 3 to R.color.grade_material_three, + 2 to R.color.grade_material_two, + 1 to R.color.grade_material_one + ) + + private val gradePointsColors = listOf( + Color.parseColor("#37c69c"), + Color.parseColor("#d8b12a") + ) + + private val gradeLabels = listOf( + "6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+" + ) + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int): Int { + return when (items[position].type) { + ViewType.SEMESTER, ViewType.PARTIAL -> R.layout.item_grade_statistics_pie + ViewType.POINTS -> R.layout.item_grade_statistics_bar + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val viewHolder = LayoutInflater.from(parent.context).inflate(viewType, parent, false) + return when (viewType) { + R.layout.item_grade_statistics_bar -> GradeStatisticsBar(viewHolder) + else -> GradeStatisticsPie(viewHolder) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is GradeStatisticsPie -> bindPieChart(holder, items[position].partial) + is GradeStatisticsBar -> bindBarChart(holder, items[position].points!!) + } + } + + private fun bindPieChart(holder: GradeStatisticsPie, partials: List) { + with(holder.view.gradeStatisticsPieTitle) { + text = partials.firstOrNull()?.subject + visibility = if (items.size == 1) GONE else VISIBLE + } + + val gradeColors = when (theme) { + "vulcan" -> vulcanGradeColors + else -> materialGradeColors + } + + val dataset = PieDataSet(partials.map { + PieEntry(it.amount.toFloat(), it.grade.toString()) + }, "Legenda") + + with(dataset) { + valueTextSize = 12f + sliceSpace = 1f + valueTextColor = Color.WHITE + setColors(partials.map { + gradeColors.single { color -> color.first == it.grade }.second + }.toIntArray(), holder.view.context) + } + + with(holder.view.gradeStatisticsPie) { + setTouchEnabled(false) + if (partials.size == 1) animateXY(1000, 1000) + data = PieData(dataset).apply { + setValueFormatter(object : ValueFormatter() { + override fun getPieLabel(value: Float, pieEntry: PieEntry): String { + return resources.getQuantityString(R.plurals.grade_number_item, value.toInt(), value.toInt()) + } + }) + } + with(legend) { + textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary) + setCustom(gradeLabels.mapIndexed { i, it -> + LegendEntry().apply { + label = it + formColor = ContextCompat.getColor(context, gradeColors[i].second) + form = Legend.LegendForm.SQUARE + } + }) + } + + minAngleForSlices = 25f + description.isEnabled = false + centerText = partials.fold(0) { acc, it -> acc + it.amount } + .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } + + setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground)) + setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary)) + invalidate() + } + } + + private fun bindBarChart(holder: GradeStatisticsBar, points: GradePointsStatistics) { + with(holder.view.gradeStatisticsBarTitle) { + text = points.subject + visibility = if (items.size == 1) GONE else VISIBLE + } + + val dataset = BarDataSet(listOf( + BarEntry(1f, points.others.toFloat()), + BarEntry(2f, points.student.toFloat()) + ), "Legenda") + + with(dataset) { + valueTextSize = 12f + valueTextColor = holder.view.context.getThemeAttrColor(android.R.attr.textColorPrimary) + valueFormatter = object : ValueFormatter() { + override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%" + } + colors = gradePointsColors + } + + with(holder.view.gradeStatisticsBar) { + setTouchEnabled(false) + if (items.size == 1) animateXY(1000, 1000) + data = BarData(dataset).apply { + barWidth = 0.5f + setFitBars(true) + } + legend.setCustom(listOf( + LegendEntry().apply { + label = "Średnia klasy" + formColor = gradePointsColors[0] + form = Legend.LegendForm.SQUARE + }, + LegendEntry().apply { + label = "Uczeń" + formColor = gradePointsColors[1] + form = Legend.LegendForm.SQUARE + } + )) + legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary) + + description.isEnabled = false + + holder.view.context.getThemeAttrColor(android.R.attr.textColorPrimary).let { + axisLeft.textColor = it + axisRight.textColor = it + } + xAxis.setDrawLabels(false) + xAxis.setDrawGridLines(false) + with(axisLeft) { + axisMinimum = 0f + axisMaximum = 100f + labelCount = 11 + } + with(axisRight) { + axisMinimum = 0f + axisMaximum = 100f + labelCount = 11 + } + invalidate() + } + } + + class GradeStatisticsPie(val view: View) : RecyclerView.ViewHolder(view) + + class GradeStatisticsBar(val view: View) : RecyclerView.ViewHolder(view) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt index 1c54f387e..c05cf3800 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt @@ -1,31 +1,18 @@ package io.github.wulkanowy.ui.modules.grade.statistics -import android.graphics.Color -import android.graphics.Color.WHITE import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.TextView -import androidx.core.content.ContextCompat -import com.github.mikephil.charting.components.Legend -import com.github.mikephil.charting.components.LegendEntry -import com.github.mikephil.charting.data.BarData -import com.github.mikephil.charting.data.BarDataSet -import com.github.mikephil.charting.data.BarEntry -import com.github.mikephil.charting.data.PieData -import com.github.mikephil.charting.data.PieDataSet -import com.github.mikephil.charting.data.PieEntry -import com.github.mikephil.charting.formatter.ValueFormatter +import androidx.recyclerview.widget.LinearLayoutManager import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.setOnItemSelectedListener import kotlinx.android.synthetic.main.fragment_grade_statistics.* import javax.inject.Inject @@ -35,6 +22,9 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G @Inject lateinit var presenter: GradeStatisticsPresenter + @Inject + lateinit var statisticsAdapter: GradeStatisticsAdapter + private lateinit var subjectsAdapter: ArrayAdapter companion object { @@ -43,9 +33,7 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G fun newInstance() = GradeStatisticsFragment() } - override val isPieViewEmpty get() = gradeStatisticsChart.isEmpty - - override val isBarViewEmpty get() = gradeStatisticsChartPoints.isEmpty + override val isViewEmpty get() = statisticsAdapter.items.isEmpty() override val currentType get() = when (gradeStatisticsTypeSwitch.checkedRadioButtonId) { @@ -54,35 +42,6 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G else -> ViewType.POINTS } - private lateinit var gradeColors: List> - - private val vulcanGradeColors = listOf( - 6 to R.color.grade_vulcan_six, - 5 to R.color.grade_vulcan_five, - 4 to R.color.grade_vulcan_four, - 3 to R.color.grade_vulcan_three, - 2 to R.color.grade_vulcan_two, - 1 to R.color.grade_vulcan_one - ) - - private val materialGradeColors = listOf( - 6 to R.color.grade_material_six, - 5 to R.color.grade_material_five, - 4 to R.color.grade_material_four, - 3 to R.color.grade_material_three, - 2 to R.color.grade_material_two, - 1 to R.color.grade_material_one - ) - - private val gradePointsColors = listOf( - Color.parseColor("#37c69c"), - Color.parseColor("#d8b12a") - ) - - private val gradeLabels = listOf( - "6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+" - ) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_grade_statistics, container, false) } @@ -94,31 +53,9 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } override fun initView() { - with(gradeStatisticsChart) { - description.isEnabled = false - setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground)) - setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary)) - animateXY(1000, 1000) - minAngleForSlices = 25f - legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary) - } - - with(gradeStatisticsChartPoints) { - description.isEnabled = false - - animateXY(1000, 1000) - legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary) - - with(axisLeft) { - axisMinimum = 0f - axisMaximum = 100f - labelCount = 11 - } - with(axisRight) { - axisMinimum = 0f - axisMaximum = 100f - labelCount = 11 - } + with(gradeStatisticsRecycler) { + layoutManager = LinearLayoutManager(requireContext()) + adapter = statisticsAdapter } subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf()) @@ -144,86 +81,10 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } } - override fun updatePieData(items: List, theme: String) { - gradeColors = when (theme) { - "vulcan" -> vulcanGradeColors - else -> materialGradeColors - } - - val dataset = PieDataSet(items.map { - PieEntry(it.amount.toFloat(), it.grade.toString()) - }, "Legenda").apply { - valueTextSize = 12f - sliceSpace = 1f - valueTextColor = WHITE - setColors(items.map { - gradeColors.single { color -> color.first == it.grade }.second - }.toIntArray(), context) - } - - with(gradeStatisticsChart) { - data = PieData(dataset).apply { - setTouchEnabled(false) - setValueFormatter(object : ValueFormatter() { - override fun getPieLabel(value: Float, pieEntry: PieEntry): String { - return resources.getQuantityString(R.plurals.grade_number_item, value.toInt(), value.toInt()) - } - }) - centerText = items.fold(0) { acc, it -> acc + it.amount } - .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } - } - legend.apply { - setCustom(gradeLabels.mapIndexed { i, it -> - LegendEntry().apply { - label = it - formColor = ContextCompat.getColor(context, gradeColors[i].second) - form = Legend.LegendForm.SQUARE - } - }) - } - invalidate() - } - } - - override fun updateBarData(item: GradePointsStatistics) { - val dataset = BarDataSet(listOf( - BarEntry(1f, item.others.toFloat()), - BarEntry(2f, item.student.toFloat()) - ), "Legenda").apply { - valueTextSize = 12f - valueTextColor = requireContext().getThemeAttrColor(android.R.attr.textColorPrimary) - valueFormatter = object : ValueFormatter() { - override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%" - } - colors = gradePointsColors - } - - with(gradeStatisticsChartPoints) { - data = BarData(dataset).apply { - barWidth = 0.5f - setFitBars(true) - } - setTouchEnabled(false) - xAxis.setDrawLabels(false) - xAxis.setDrawGridLines(false) - requireContext().getThemeAttrColor(android.R.attr.textColorPrimary).let { - axisLeft.textColor = it - axisRight.textColor = it - } - legend.setCustom(listOf( - LegendEntry().apply { - label = "Średnia klasy" - formColor = gradePointsColors[0] - form = Legend.LegendForm.SQUARE - }, - LegendEntry().apply { - label = "Uczeń" - formColor = gradePointsColors[1] - form = Legend.LegendForm.SQUARE - } - )) - invalidate() - } + override fun updateData(items: List, theme: String) { + statisticsAdapter.theme = theme + statisticsAdapter.items = items + statisticsAdapter.notifyDataSetChanged() } override fun showSubjects(show: Boolean) { @@ -232,16 +93,15 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } override fun clearView() { - gradeStatisticsChart.clear() - gradeStatisticsChartPoints.clear() + statisticsAdapter.items = emptyList() } - override fun showPieContent(show: Boolean) { - gradeStatisticsChart.visibility = if (show) View.VISIBLE else View.GONE + override fun resetView() { + gradeStatisticsScroll.scrollTo(0, 0) } - override fun showBarContent(show: Boolean) { - gradeStatisticsChartPoints.visibility = if (show) View.VISIBLE else View.GONE + override fun showContent(show: Boolean) { + gradeStatisticsRecycler.visibility = if (show) View.VISIBLE else View.GONE } override fun showEmpty(show: Boolean) { @@ -273,7 +133,7 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G } override fun onParentReselected() { - // + presenter.onParentViewReselected() } override fun onParentChangeSemester() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index 833782619..4ec0fafe0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -48,12 +48,19 @@ class GradeStatisticsPresenter @Inject constructor( loadDataByType(semesterId, currentSubjectName, currentType, forceRefresh) } + + fun onParentViewReselected() { + view?.run { + if (!isViewEmpty) resetView() + } + } + fun onParentViewChangeSemester() { view?.run { showProgress(true) enableSwipe(false) showRefresh(false) - showBarContent(false) + showContent(false) showErrorView(false) showEmpty(false) clearView() @@ -81,8 +88,7 @@ class GradeStatisticsPresenter @Inject constructor( fun onSubjectSelected(name: String?) { Timber.i("Select grade stats subject $name") view?.run { - showBarContent(false) - showPieContent(false) + showContent(false) showProgress(true) enableSwipe(false) showEmpty(false) @@ -99,8 +105,7 @@ class GradeStatisticsPresenter @Inject constructor( Timber.i("Select grade stats semester: $type") disposable.clear() view?.run { - showBarContent(false) - showPieContent(false) + showContent(false) showProgress(true) enableSwipe(false) showEmpty(false) @@ -135,20 +140,22 @@ class GradeStatisticsPresenter @Inject constructor( private fun loadDataByType(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean = false) { currentSubjectName = subjectName currentType = type - when (type) { - ViewType.SEMESTER -> loadData(semesterId, subjectName, true, forceRefresh) - ViewType.PARTIAL -> loadData(semesterId, subjectName, false, forceRefresh) - ViewType.POINTS -> loadPointsData(semesterId, subjectName, forceRefresh) - } + loadData(semesterId, subjectName, type, forceRefresh) } - private fun loadData(semesterId: Int, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false) { + private fun loadData(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean) { Timber.i("Loading grade stats data started") disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getSemesters(it) } - .flatMap { gradeStatisticsRepository.getGradesStatistics(it.first { item -> item.semesterId == semesterId }, subjectName, isSemester, forceRefresh) } - .map { list -> list.sortedByDescending { it.grade } } - .map { list -> list.filter { it.amount != 0 } } + .flatMap { + val semester = it.first { item -> item.semesterId == semesterId } + + when (type) { + ViewType.SEMESTER -> gradeStatisticsRepository.getGradesStatistics(semester, subjectName, true, forceRefresh) + ViewType.PARTIAL -> gradeStatisticsRepository.getGradesStatistics(semester, subjectName, false, forceRefresh) + ViewType.POINTS -> gradeStatisticsRepository.getGradesPointsStatistics(semester, subjectName, forceRefresh) + } + } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { @@ -163,10 +170,9 @@ class GradeStatisticsPresenter @Inject constructor( Timber.i("Loading grade stats result: Success") view?.run { showEmpty(it.isEmpty()) - showBarContent(false) - showPieContent(it.isNotEmpty()) + showContent(it.isNotEmpty()) showErrorView(false) - updatePieData(it, preferencesRepository.gradeColorTheme) + updateData(it, preferencesRepository.gradeColorTheme) } analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh) }) { @@ -175,47 +181,9 @@ class GradeStatisticsPresenter @Inject constructor( }) } - private fun loadPointsData(semesterId: Int, subjectName: String, forceRefresh: Boolean = false) { - Timber.i("Loading grade points stats data started") - disposable.add(studentRepository.getCurrentStudent() - .flatMap { semesterRepository.getSemesters(it) } - .flatMapMaybe { gradeStatisticsRepository.getGradesPointsStatistics(it.first { item -> item.semesterId == semesterId }, subjectName, forceRefresh) } - .subscribeOn(schedulers.backgroundThread) - .observeOn(schedulers.mainThread) - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) - } - } - .subscribe({ - Timber.i("Loading grade points stats result: Success") - view?.run { - showEmpty(false) - showPieContent(false) - showBarContent(true) - showErrorView(false) - updateBarData(it) - } - analytics.logEvent("load_grade_points_statistics", "force_refresh" to forceRefresh) - }, { - Timber.e("Loading grade points stats result: An exception occurred") - errorHandler.dispatch(it) - }, { - Timber.d("Loading grade points stats result: No point stats found") - view?.run { - showBarContent(false) - showEmpty(true) - } - }) - ) - } - private fun showErrorViewOnError(message: String, error: Throwable) { view?.run { - if ((isBarViewEmpty && currentType == ViewType.POINTS) || (isPieViewEmpty) && currentType != ViewType.POINTS) { + if (isViewEmpty) { lastError = error setErrorDetails(message) showErrorView(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt index 003520fbd..9ba8524c2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsView.kt @@ -1,14 +1,11 @@ package io.github.wulkanowy.ui.modules.grade.statistics -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.ui.base.BaseView interface GradeStatisticsView : BaseView { - val isPieViewEmpty: Boolean - - val isBarViewEmpty: Boolean + val isViewEmpty: Boolean val currentType: ViewType @@ -16,9 +13,7 @@ interface GradeStatisticsView : BaseView { fun updateSubjects(data: ArrayList) - fun updatePieData(items: List, theme: String) - - fun updateBarData(item: GradePointsStatistics) + fun updateData(items: List, theme: String) fun showSubjects(show: Boolean) @@ -28,9 +23,9 @@ interface GradeStatisticsView : BaseView { fun clearView() - fun showPieContent(show: Boolean) + fun resetView() - fun showBarContent(show: Boolean) + fun showContent(show: Boolean) fun showEmpty(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/utils/NumberExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/NumberExtension.kt deleted file mode 100644 index 12350a42a..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/NumberExtension.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.wulkanowy.utils - -import kotlin.math.round - -fun Double.roundToDecimalPlaces(places: Int = 2): Double { - return round(this * 10 * places) / (10 * places) -} diff --git a/app/src/main/res/layout/fragment_grade_statistics.xml b/app/src/main/res/layout/fragment_grade_statistics.xml index 3ed80d106..ecc2e3e00 100644 --- a/app/src/main/res/layout/fragment_grade_statistics.xml +++ b/app/src/main/res/layout/fragment_grade_statistics.xml @@ -37,6 +37,7 @@ android:layout_height="wrap_content"> @@ -90,25 +91,11 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - + tools:listitem="@layout/item_grade_statistics_pie" /> + + + + + + diff --git a/app/src/main/res/layout/item_grade_statistics_pie.xml b/app/src/main/res/layout/item_grade_statistics_pie.xml new file mode 100644 index 000000000..15992a37e --- /dev/null +++ b/app/src/main/res/layout/item_grade_statistics_pie.xml @@ -0,0 +1,27 @@ + + + + + + +