forked from github/wulkanowy-mirror
Show list of charts in grade statistics (#689)
This commit is contained in:
parent
e61c2bced8
commit
be057dd63c
@ -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)
|
||||
|
@ -11,7 +11,7 @@ import javax.inject.Singleton
|
||||
interface GradePointsStatisticsDao : BaseDao<GradePointsStatistics> {
|
||||
|
||||
@Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName")
|
||||
fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Maybe<GradePointsStatistics>
|
||||
fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Maybe<List<GradePointsStatistics>>
|
||||
|
||||
@Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId")
|
||||
fun loadAll(semesterId: Int, studentId: Int): Maybe<List<GradePointsStatistics>>
|
||||
|
@ -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<GradeStatistics>,
|
||||
|
||||
val points: GradePointsStatistics?
|
||||
)
|
@ -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<GradePointsStatistics> {
|
||||
fun getGradesPointsStatistics(semester: Semester, subjectName: String): Maybe<List<GradePointsStatistics>> {
|
||||
return when (subjectName) {
|
||||
"Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).flatMap { list ->
|
||||
if (list.isEmpty()) return@flatMap Maybe.empty<GradePointsStatistics>()
|
||||
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<GradeStatistics>) {
|
||||
|
@ -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<List<GradeStatistics>> {
|
||||
return local.getGradesStatistics(semester, isSemester, subjectName).filter { !forceRefresh }
|
||||
fun getGradesStatistics(semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false): Single<List<GradeStatisticsItem>> {
|
||||
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<GradePointsStatistics> {
|
||||
return local.getGradesPointsStatistics(semester, subjectName).filter { !forceRefresh }
|
||||
fun getGradesPointsStatistics(semester: Semester, subjectName: String, forceRefresh: Boolean): Single<List<GradeStatisticsItem>> {
|
||||
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<GradeStatistics>.mapToStatisticItems(): List<GradeStatisticsItem> {
|
||||
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<GradePointsStatistics>.mapToStatisticsItem(): List<GradeStatisticsItem> {
|
||||
return map {
|
||||
GradeStatisticsItem(
|
||||
type = ViewType.POINTS,
|
||||
partial = emptyList(),
|
||||
points = it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<RecyclerView.ViewHolder>() {
|
||||
|
||||
var items = emptyList<GradeStatisticsItem>()
|
||||
|
||||
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<GradeStatistics>) {
|
||||
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)
|
||||
}
|
@ -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<String>
|
||||
|
||||
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<Pair<Int, Int>>
|
||||
|
||||
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<GradeStatistics>, 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<GradeStatisticsItem>, 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() {
|
||||
|
@ -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)
|
||||
|
@ -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<String>)
|
||||
|
||||
fun updatePieData(items: List<GradeStatistics>, theme: String)
|
||||
|
||||
fun updateBarData(item: GradePointsStatistics)
|
||||
fun updateData(items: List<GradeStatisticsItem>, 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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -37,6 +37,7 @@
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/gradeStatisticsScroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
@ -90,25 +91,11 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.github.mikephil.charting.charts.PieChart
|
||||
android:id="@+id/gradeStatisticsChart"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gradeStatisticsRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="10dp"
|
||||
android:background="?android:windowBackground"
|
||||
android:minHeight="400dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.github.mikephil.charting.charts.BarChart
|
||||
android:id="@+id/gradeStatisticsChartPoints"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="10dp"
|
||||
android:background="?android:windowBackground"
|
||||
android:minHeight="400dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
tools:listitem="@layout/item_grade_statistics_pie" />
|
||||
|
||||
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
|
||||
android:id="@+id/gradeStatisticsProgress"
|
||||
|
27
app/src/main/res/layout/item_grade_statistics_bar.xml
Normal file
27
app/src/main/res/layout/item_grade_statistics_bar.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gradeStatisticsBarTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorControlHighlight"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:textSize="18sp"
|
||||
tools:text="Matematyka" />
|
||||
|
||||
<com.github.mikephil.charting.charts.BarChart
|
||||
android:id="@+id/gradeStatisticsBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="400dp"
|
||||
android:layout_margin="10dp"
|
||||
android:background="?android:windowBackground"
|
||||
tools:context=".ui.modules.grade.statistics.GradeStatisticsAdapter" />
|
||||
|
||||
</LinearLayout>
|
27
app/src/main/res/layout/item_grade_statistics_pie.xml
Normal file
27
app/src/main/res/layout/item_grade_statistics_pie.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gradeStatisticsPieTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorControlHighlight"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:textSize="18sp"
|
||||
tools:text="Matematyka" />
|
||||
|
||||
<com.github.mikephil.charting.charts.PieChart
|
||||
android:id="@+id/gradeStatisticsPie"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="400dp"
|
||||
android:layout_margin="10dp"
|
||||
android:background="?android:windowBackground"
|
||||
tools:context=".ui.modules.grade.statistics.GradeStatisticsAdapter" />
|
||||
|
||||
</LinearLayout>
|
Loading…
x
Reference in New Issue
Block a user