forked from github/wulkanowy-mirror
Add grade statistics (#251)
This commit is contained in:

committed by
Rafał Borcz

parent
cae4f140e6
commit
dcab8df4b9
@ -82,6 +82,10 @@ internal class RepositoryModule {
|
||||
@Provides
|
||||
fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideMessagesDao(database: AppDatabase) = database.messagesDao
|
||||
|
@ -11,6 +11,7 @@ import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeStatisticsDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.HomeworkDao
|
||||
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
|
||||
@ -27,6 +28,7 @@ import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
@ -43,6 +45,7 @@ import io.github.wulkanowy.data.db.migrations.Migration3
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@ -56,6 +59,7 @@ import javax.inject.Singleton
|
||||
AttendanceSummary::class,
|
||||
Grade::class,
|
||||
GradeSummary::class,
|
||||
GradeStatistics::class,
|
||||
Message::class,
|
||||
Note::class,
|
||||
Homework::class,
|
||||
@ -72,7 +76,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 6
|
||||
const val VERSION_SCHEMA = 7
|
||||
|
||||
fun newInstance(context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
|
||||
@ -84,7 +88,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration3(),
|
||||
Migration4(),
|
||||
Migration5(),
|
||||
Migration6()
|
||||
Migration6(),
|
||||
Migration7()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
@ -106,6 +111,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract val gradeSummaryDao: GradeSummaryDao
|
||||
|
||||
abstract val gradeStatistics: GradeStatisticsDao
|
||||
|
||||
abstract val messagesDao: MessagesDao
|
||||
|
||||
abstract val noteDao: NoteDao
|
||||
|
@ -0,0 +1,26 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.reactivex.Maybe
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Dao
|
||||
interface GradeStatisticsDao {
|
||||
|
||||
@Insert
|
||||
fun insertAll(gradesStatistics: List<GradeStatistics>)
|
||||
|
||||
@Delete
|
||||
fun deleteAll(gradesStatistics: List<GradeStatistics>)
|
||||
|
||||
@Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName AND is_semester = :isSemester")
|
||||
fun loadSubject(semesterId: Int, studentId: Int, subjectName: String, isSemester: Boolean): Maybe<List<GradeStatistics>>
|
||||
|
||||
@Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND is_semester = :isSemester")
|
||||
fun loadAll(semesterId: Int, studentId: Int, isSemester: Boolean): Maybe<List<GradeStatistics>>
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "GradesStatistics")
|
||||
data class GradeStatistics(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
|
||||
@ColumnInfo(name = "semester_id")
|
||||
val semesterId: Int,
|
||||
|
||||
val subject: String,
|
||||
|
||||
val grade: Int,
|
||||
|
||||
val amount: Int,
|
||||
|
||||
@ColumnInfo(name = "is_semester")
|
||||
val semester: Boolean
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration7 : Migration(6, 7) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `GradesStatistics` (" +
|
||||
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
|
||||
"`student_id` INTEGER NOT NULL," +
|
||||
"`semester_id` INTEGER NOT NULL," +
|
||||
"`subject` TEXT NOT NULL," +
|
||||
"`grade` INTEGER NOT NULL," +
|
||||
"`amount` INTEGER NOT NULL," +
|
||||
"`is_semester` INTEGER NOT NULL)")
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package io.github.wulkanowy.data.repositories.gradestatistics
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.GradeStatisticsDao
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.reactivex.Maybe
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class GradeStatisticsLocal @Inject constructor(private val gradeStatisticsDb: GradeStatisticsDao) {
|
||||
|
||||
fun getGradesStatistics(semester: Semester, isSemester: Boolean): Maybe<List<GradeStatistics>> {
|
||||
return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester)
|
||||
.filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
fun getGradesStatistics(semester: Semester, isSemester: Boolean, subjectName: String): Maybe<List<GradeStatistics>> {
|
||||
return (if ("Wszystkie" == subjectName) gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).map { list ->
|
||||
list.groupBy { it.grade }.map {
|
||||
GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, it.value.fold(0) { acc, e -> acc + e.amount }, false)
|
||||
}
|
||||
}
|
||||
else gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester)).filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
fun saveGradesStatistics(gradesStatistics: List<GradeStatistics>) {
|
||||
gradeStatisticsDb.insertAll(gradesStatistics)
|
||||
}
|
||||
|
||||
fun deleteGradesStatistics(gradesStatistics: List<GradeStatistics>) {
|
||||
gradeStatisticsDb.deleteAll(gradesStatistics)
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package io.github.wulkanowy.data.repositories.gradestatistics
|
||||
|
||||
import io.github.wulkanowy.api.Api
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class GradeStatisticsRemote @Inject constructor(private val api: Api) {
|
||||
|
||||
fun getGradeStatistics(semester: Semester, isSemester: Boolean): Single<List<GradeStatistics>> {
|
||||
return Single.just(api.apply { diaryId = semester.diaryId })
|
||||
.flatMap { it.getGradesStatistics(semester.semesterId, isSemester) }
|
||||
.map { gradeStatistics ->
|
||||
gradeStatistics.map {
|
||||
GradeStatistics(
|
||||
semesterId = semester.semesterId,
|
||||
studentId = semester.studentId,
|
||||
subject = it.subject,
|
||||
grade = it.gradeValue,
|
||||
amount = it.amount ?: 0,
|
||||
semester = isSemester
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package io.github.wulkanowy.data.repositories.gradestatistics
|
||||
|
||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.reactivex.Single
|
||||
import java.net.UnknownHostException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class GradeStatisticsRepository @Inject constructor(
|
||||
private val settings: InternetObservingSettings,
|
||||
private val local: GradeStatisticsLocal,
|
||||
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 }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
if (it) remote.getGradeStatistics(semester, isSemester)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { newGradesStats ->
|
||||
local.getGradesStatistics(semester, isSemester).toSingle(emptyList())
|
||||
.doOnSuccess { oldGradesStats ->
|
||||
local.deleteGradesStatistics(oldGradesStats - newGradesStats)
|
||||
local.saveGradesStatistics(newGradesStats - oldGradesStats)
|
||||
}
|
||||
}.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).toSingle(emptyList()) })
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SubjectRepostory @Inject constructor(
|
||||
class SubjectRepository @Inject constructor(
|
||||
private val settings: InternetObservingSettings,
|
||||
private val local: SubjectLocal,
|
||||
private val remote: SubjectRemote
|
@ -5,7 +5,7 @@ import io.github.wulkanowy.data.db.entities.Subject
|
||||
import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.subject.SubjectRepostory
|
||||
import io.github.wulkanowy.data.repositories.subject.SubjectRepository
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
|
||||
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
@ -21,7 +21,7 @@ import javax.inject.Inject
|
||||
class AttendanceSummaryPresenter @Inject constructor(
|
||||
private val errorHandler: SessionErrorHandler,
|
||||
private val attendanceSummaryRepository: AttendanceSummaryRepository,
|
||||
private val subjectRepository: SubjectRepostory,
|
||||
private val subjectRepository: SubjectRepository,
|
||||
private val studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val schedulers: SchedulersProvider,
|
||||
|
@ -0,0 +1,34 @@
|
||||
package io.github.wulkanowy.ui.modules.grade
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
|
||||
/**
|
||||
* @see <a href="https://stackoverflow.com/a/50382854">Tabs don't fit to screen with tabmode=scrollable, Even with a Custom Tab Layout</a>
|
||||
*/
|
||||
class CustomTabLayout : TabLayout {
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
|
||||
val tabLayout = getChildAt(0) as ViewGroup
|
||||
val childCount = tabLayout.childCount
|
||||
|
||||
if (childCount == 0) return
|
||||
|
||||
val tabMinWidth = context.resources.displayMetrics.widthPixels / childCount
|
||||
|
||||
for (i in 0 until childCount) {
|
||||
tabLayout.getChildAt(i).minimumWidth = tabMinWidth
|
||||
}
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.setOnSelectPageListener
|
||||
@ -63,12 +64,14 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView,
|
||||
containerId = gradeViewPager.id
|
||||
addFragmentsWithTitle(mapOf(
|
||||
GradeDetailsFragment.newInstance() to getString(R.string.all_details),
|
||||
GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary)
|
||||
GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary),
|
||||
GradeStatisticsFragment.newInstance() to getString(R.string.grade_menu_statistics)
|
||||
))
|
||||
}
|
||||
|
||||
gradeViewPager.run {
|
||||
adapter = pagerAdapter
|
||||
offscreenPageLimit = 3
|
||||
setOnSelectPageListener { presenter.onPageSelected(it) }
|
||||
}
|
||||
gradeTabLayout.setupWithViewPager(gradeViewPager)
|
||||
|
@ -7,6 +7,7 @@ import io.github.wulkanowy.di.scopes.PerChildFragment
|
||||
import io.github.wulkanowy.di.scopes.PerFragment
|
||||
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
|
||||
import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
|
||||
|
||||
@Module
|
||||
@ -28,4 +29,8 @@ abstract class GradeModule {
|
||||
@PerChildFragment
|
||||
@ContributesAndroidInjector
|
||||
abstract fun binGradeSummaryFragment(): GradeSummaryFragment
|
||||
|
||||
@PerChildFragment
|
||||
@ContributesAndroidInjector
|
||||
abstract fun binGradeStatisticsFragment(): GradeStatisticsFragment
|
||||
}
|
||||
|
@ -114,6 +114,6 @@ class GradePresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun notifyChildrenSemesterChange() {
|
||||
for (i in 0..1) view?.notifyChildSemesterChange(i)
|
||||
for (i in 0..2) view?.notifyChildSemesterChange(i)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,191 @@
|
||||
package io.github.wulkanowy.ui.modules.grade.statistics
|
||||
|
||||
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.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.GradeStatistics
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeView
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
||||
import kotlinx.android.synthetic.main.fragment_grade_statistics.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class GradeStatisticsFragment : BaseSessionFragment(), GradeStatisticsView, GradeView.GradeChildView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: GradeStatisticsPresenter
|
||||
|
||||
private lateinit var subjectsAdapter: ArrayAdapter<String>
|
||||
|
||||
companion object {
|
||||
private const val SAVED_CHART_TYPE = "CURRENT_TYPE"
|
||||
|
||||
fun newInstance() = GradeStatisticsFragment()
|
||||
}
|
||||
|
||||
override val isViewEmpty
|
||||
get() = gradeStatisticsChart.isEmpty
|
||||
|
||||
private val gradeColors = 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 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)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
messageContainer = gradeStatisticsChart
|
||||
presenter.onAttachView(this, savedInstanceState?.getBoolean(SAVED_CHART_TYPE))
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
gradeStatisticsChart.run {
|
||||
description.isEnabled = false
|
||||
setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground))
|
||||
setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary))
|
||||
animateXY(1000, 1000)
|
||||
minAngleForSlices = 25f
|
||||
legend.apply {
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
context?.let {
|
||||
subjectsAdapter = ArrayAdapter(it, android.R.layout.simple_spinner_item, ArrayList<String>())
|
||||
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
|
||||
}
|
||||
|
||||
gradeStatisticsSubjects.run {
|
||||
adapter = subjectsAdapter
|
||||
setOnItemSelectedListener { presenter.onSubjectSelected((it as TextView).text.toString()) }
|
||||
}
|
||||
|
||||
gradeStatisticsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
}
|
||||
|
||||
override fun updateSubjects(data: ArrayList<String>) {
|
||||
subjectsAdapter.run {
|
||||
clear()
|
||||
addAll(data)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(items: List<GradeStatistics>) {
|
||||
gradeStatisticsChart.run {
|
||||
data = PieData(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)
|
||||
}).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) }
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showSubjects(show: Boolean) {
|
||||
gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun clearView() {
|
||||
gradeStatisticsChart.clear()
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
gradeStatisticsChart.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showEmpty(show: Boolean) {
|
||||
gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showRefresh(show: Boolean) {
|
||||
gradeStatisticsSwipe.isRefreshing = show
|
||||
}
|
||||
|
||||
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||
presenter.onParentViewLoadData(semesterId, forceRefresh)
|
||||
}
|
||||
|
||||
override fun onParentReselected() {
|
||||
//
|
||||
}
|
||||
|
||||
override fun onParentChangeSemester() {
|
||||
presenter.onParentViewChangeSemester()
|
||||
}
|
||||
|
||||
override fun notifyParentDataLoaded(semesterId: Int) {
|
||||
(parentFragment as? GradeFragment)?.onChildFragmentLoaded(semesterId)
|
||||
}
|
||||
|
||||
override fun notifyParentRefresh() {
|
||||
(parentFragment as? GradeFragment)?.onChildRefresh()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
|
||||
presenter.onTypeChange(checkedId == R.id.gradeStatisticsTypeSemester)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putBoolean(GradeStatisticsFragment.SAVED_CHART_TYPE, presenter.currentIsSemester)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
presenter.onDetachView()
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package io.github.wulkanowy.ui.modules.grade.statistics
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Subject
|
||||
import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.subject.SubjectRepository
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
|
||||
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class GradeStatisticsPresenter @Inject constructor(
|
||||
private val errorHandler: SessionErrorHandler,
|
||||
private val gradeStatisticsRepository: GradeStatisticsRepository,
|
||||
private val subjectRepository: SubjectRepository,
|
||||
private val studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val schedulers: SchedulersProvider,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
) : BaseSessionPresenter<GradeStatisticsView>(errorHandler) {
|
||||
|
||||
private var subjects = emptyList<Subject>()
|
||||
|
||||
private var currentSemesterId = 0
|
||||
|
||||
private var currentSubjectName: String = "Wszystkie"
|
||||
|
||||
var currentIsSemester = false
|
||||
private set
|
||||
|
||||
fun onAttachView(view: GradeStatisticsView, isSemester: Boolean?) {
|
||||
super.onAttachView(view)
|
||||
currentIsSemester = isSemester ?: false
|
||||
view.initView()
|
||||
}
|
||||
|
||||
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||
currentSemesterId = semesterId
|
||||
loadSubjects()
|
||||
loadData(semesterId, currentSubjectName, currentIsSemester, forceRefresh)
|
||||
}
|
||||
|
||||
fun onParentViewChangeSemester() {
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
showRefresh(false)
|
||||
showContent(false)
|
||||
showEmpty(false)
|
||||
clearView()
|
||||
}
|
||||
disposable.clear()
|
||||
}
|
||||
|
||||
fun onSwipeRefresh() {
|
||||
Timber.i("Force refreshing the grade stats")
|
||||
view?.notifyParentRefresh()
|
||||
}
|
||||
|
||||
fun onSubjectSelected(name: String) {
|
||||
Timber.i("Select attendance stats subject $name")
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
showEmpty(false)
|
||||
clearView()
|
||||
}
|
||||
(subjects.singleOrNull { it.name == name }?.name).let {
|
||||
if (it != currentSubjectName) loadData(currentSemesterId, name, currentIsSemester)
|
||||
}
|
||||
}
|
||||
|
||||
fun onTypeChange(isSemester: Boolean) {
|
||||
Timber.i("Select attendance stats semester: $isSemester")
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
showEmpty(false)
|
||||
clearView()
|
||||
}
|
||||
loadData(currentSemesterId, currentSubjectName, isSemester)
|
||||
}
|
||||
|
||||
private fun loadSubjects() {
|
||||
Timber.i("Loading grade stats subjects started")
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
.flatMap { semesterRepository.getCurrentSemester(it) }
|
||||
.flatMap { subjectRepository.getSubjects(it) }
|
||||
.doOnSuccess { subjects = it }
|
||||
.map { ArrayList(it.map { subject -> subject.name }) }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.subscribe({
|
||||
Timber.i("Loading grade stats subjects result: Success")
|
||||
view?.run {
|
||||
updateSubjects(it)
|
||||
showSubjects(true)
|
||||
}
|
||||
}, {
|
||||
Timber.e("Loading grade stats subjects result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadData(semesterId: Int, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false) {
|
||||
Timber.i("Loading grade stats data started")
|
||||
currentSubjectName = subjectName
|
||||
currentIsSemester = isSemester
|
||||
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 } }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.doFinally {
|
||||
view?.run {
|
||||
showRefresh(false)
|
||||
showProgress(false)
|
||||
notifyParentDataLoaded(semesterId)
|
||||
}
|
||||
}
|
||||
.subscribe({
|
||||
Timber.i("Loading grade stats result: Success")
|
||||
view?.run {
|
||||
showEmpty(it.isEmpty())
|
||||
showContent(it.isNotEmpty())
|
||||
updateData(it)
|
||||
}
|
||||
analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
}) {
|
||||
Timber.e("Loading grade stats result: An exception occurred")
|
||||
view?.run { showEmpty(isViewEmpty) }
|
||||
errorHandler.dispatch(it)
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.github.wulkanowy.ui.modules.grade.statistics
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||
import io.github.wulkanowy.ui.base.session.BaseSessionView
|
||||
|
||||
interface GradeStatisticsView : BaseSessionView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateSubjects(data: ArrayList<String>)
|
||||
|
||||
fun updateData(items: List<GradeStatistics>)
|
||||
|
||||
fun showSubjects(show: Boolean)
|
||||
|
||||
fun notifyParentDataLoaded(semesterId: Int)
|
||||
|
||||
fun notifyParentRefresh()
|
||||
|
||||
fun clearView()
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showRefresh(show: Boolean)
|
||||
}
|
@ -4,11 +4,19 @@ import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Spinner
|
||||
|
||||
/**
|
||||
* @see <a href="https://stackoverflow.com/a/29602298">How to keep onItemSelected from firing off on a newly instantiated Spinner?</a>
|
||||
*/
|
||||
inline fun Spinner.setOnItemSelectedListener(crossinline listener: (view: View?) -> Unit) {
|
||||
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
listener(view)
|
||||
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
listener(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user