1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2025-01-31 13:08:21 +01:00

Make GradeAverageProvider reactive to configuration changes (#1698)

Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
This commit is contained in:
Michael 2023-03-18 15:10:12 +01:00 committed by GitHub
parent a2a31df98e
commit b3c6e2004b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 632 additions and 275 deletions

View File

@ -2,9 +2,11 @@ package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.annotation.StringRes
import androidx.core.content.edit import androidx.core.content.edit
import com.fredporciuncula.flow.preferences.FlowSharedPreferences import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference import com.fredporciuncula.flow.preferences.Preference
import com.fredporciuncula.flow.preferences.Serializer
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.* import io.github.wulkanowy.data.enums.*
@ -35,20 +37,29 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_attendance_present R.bool.pref_default_attendance_present
) )
val gradeAverageMode: GradeAverageMode private val gradeAverageModePref: Preference<GradeAverageMode>
get() = GradeAverageMode.getByValue( get() = getObjectFlow(
getString( R.string.pref_key_grade_average_mode,
R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode,
R.string.pref_default_grade_average_mode object : Serializer<GradeAverageMode> {
) override fun serialize(value: GradeAverageMode) = value.value
override fun deserialize(serialized: String) =
GradeAverageMode.getByValue(serialized)
},
) )
val gradeAverageForceCalc: Boolean val gradeAverageModeFlow: Flow<GradeAverageMode>
get() = getBoolean( get() = gradeAverageModePref.asFlow()
R.string.pref_key_grade_average_force_calc,
R.bool.pref_default_grade_average_force_calc private val gradeAverageForceCalcPref: Preference<Boolean>
get() = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_grade_average_force_calc),
context.resources.getBoolean(R.bool.pref_default_grade_average_force_calc)
) )
val gradeAverageForceCalcFlow: Flow<Boolean>
get() = gradeAverageForceCalcPref.asFlow()
val gradeExpandMode: GradeExpandMode val gradeExpandMode: GradeExpandMode
get() = GradeExpandMode.getByValue( get() = GradeExpandMode.getByValue(
getString( getString(
@ -138,12 +149,24 @@ class PreferencesRepository @Inject constructor(
R.string.pref_default_grade_modifier_plus R.string.pref_default_grade_modifier_plus
).toDouble() ).toDouble()
val gradePlusModifierFlow: Flow<Double>
get() = getStringFlow(
R.string.pref_key_grade_modifier_plus,
R.string.pref_default_grade_modifier_plus
).asFlow().map { it.toDouble() }
val gradeMinusModifier: Double val gradeMinusModifier: Double
get() = getString( get() = getString(
R.string.pref_key_grade_modifier_minus, R.string.pref_key_grade_modifier_minus,
R.string.pref_default_grade_modifier_minus R.string.pref_default_grade_modifier_minus
).toDouble() ).toDouble()
val gradeMinusModifierFlow: Flow<Double>
get() = getStringFlow(
R.string.pref_key_grade_modifier_minus,
R.string.pref_default_grade_modifier_minus
).asFlow().map { it.toDouble() }
val fillMessageContent: Boolean val fillMessageContent: Boolean
get() = getBoolean( get() = getBoolean(
R.string.pref_key_fill_message_content, R.string.pref_key_fill_message_content,
@ -191,11 +214,11 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_subjects_without_grades R.bool.pref_default_subjects_without_grades
) )
val isOptionalArithmeticAverage: Boolean val isOptionalArithmeticAverageFlow: Flow<Boolean>
get() = getBoolean( get() = flowSharedPref.getBoolean(
R.string.pref_key_optional_arithmetic_average, context.getString(R.string.pref_key_optional_arithmetic_average),
R.bool.pref_default_optional_arithmetic_average context.resources.getBoolean(R.bool.pref_default_optional_arithmetic_average)
) ).asFlow()
var lasSyncDate: Instant? var lasSyncDate: Instant?
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date) get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
@ -342,6 +365,21 @@ class PreferencesRepository @Inject constructor(
private fun getLong(id: String, default: Int) = private fun getLong(id: String, default: Int) =
sharedPref.getLong(id, context.resources.getString(default).toLong()) sharedPref.getLong(id, context.resources.getString(default).toLong())
private fun getStringFlow(id: Int, default: Int) =
flowSharedPref.getString(context.getString(id), context.getString(default))
private fun <T : Any> getObjectFlow(
@StringRes id: Int,
@StringRes default: Int,
serializer: Serializer<T>
): Preference<T> = flowSharedPref.getObject(
key = context.getString(id),
serializer = serializer,
defaultValue = serializer.deserialize(
flowSharedPref.getString(context.getString(default)).get()
)
)
private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
private fun getString(id: String, default: Int) = private fun getString(id: String, default: Int) =

View File

@ -12,70 +12,91 @@ import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.* import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.*
import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier import io.github.wulkanowy.utils.changeModifier
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest
import javax.inject.Inject import javax.inject.Inject
@OptIn(FlowPreview::class) @OptIn(ExperimentalCoroutinesApi::class)
class GradeAverageProvider @Inject constructor( class GradeAverageProvider @Inject constructor(
private val semesterRepository: SemesterRepository, private val semesterRepository: SemesterRepository,
private val gradeRepository: GradeRepository, private val gradeRepository: GradeRepository,
private val preferencesRepository: PreferencesRepository private val preferencesRepository: PreferencesRepository
) { ) {
private val plusModifier get() = preferencesRepository.gradePlusModifier private data class AverageCalcParams(
val gradeAverageMode: GradeAverageMode,
val forceAverageCalc: Boolean,
val isOptionalArithmeticAverage: Boolean,
val plusModifier: Double,
val minusModifier: Double,
)
private val minusModifier get() = preferencesRepository.gradeMinusModifier fun getGradesDetailsWithAverage(
student: Student,
private val isOptionalArithmeticAverage get() = preferencesRepository.isOptionalArithmeticAverage semesterId: Int,
forceRefresh: Boolean
fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) = ): Flow<Resource<List<GradeSubject>>> = combine(
flow = preferencesRepository.gradeAverageModeFlow,
flow2 = preferencesRepository.gradeAverageForceCalcFlow,
flow3 = preferencesRepository.isOptionalArithmeticAverageFlow,
flow4 = preferencesRepository.gradePlusModifierFlow,
flow5 = preferencesRepository.gradeMinusModifierFlow,
) { gradeAverageMode, forceAverageCalc, isOptionalArithmeticAverage, plusModifier, minusModifier ->
AverageCalcParams(
gradeAverageMode = gradeAverageMode,
forceAverageCalc = forceAverageCalc,
isOptionalArithmeticAverage = isOptionalArithmeticAverage,
plusModifier = plusModifier,
minusModifier = minusModifier,
)
}.flatMapLatest { params ->
flatResourceFlow { flatResourceFlow {
val semesters = semesterRepository.getSemesters(student) val semesters = semesterRepository.getSemesters(student)
when (params.gradeAverageMode) {
when (preferencesRepository.gradeAverageMode) {
ONE_SEMESTER -> getGradeSubjects( ONE_SEMESTER -> getGradeSubjects(
student = student, student = student,
semester = semesters.single { it.semesterId == semesterId }, semester = semesters.single { it.semesterId == semesterId },
forceRefresh = forceRefresh forceRefresh = forceRefresh,
params = params,
) )
BOTH_SEMESTERS -> calculateCombinedAverage( BOTH_SEMESTERS -> calculateCombinedAverage(
student = student, student = student,
semesters = semesters, semesters = semesters,
semesterId = semesterId, semesterId = semesterId,
forceRefresh = forceRefresh, forceRefresh = forceRefresh,
averageMode = BOTH_SEMESTERS config = params,
) )
ALL_YEAR -> calculateCombinedAverage( ALL_YEAR -> calculateCombinedAverage(
student = student, student = student,
semesters = semesters, semesters = semesters,
semesterId = semesterId, semesterId = semesterId,
forceRefresh = forceRefresh, forceRefresh = forceRefresh,
averageMode = ALL_YEAR config = params,
) )
} }
}.distinctUntilChanged() }
}
private fun calculateCombinedAverage( private fun calculateCombinedAverage(
student: Student, student: Student,
semesters: List<Semester>, semesters: List<Semester>,
semesterId: Int, semesterId: Int,
forceRefresh: Boolean, forceRefresh: Boolean,
averageMode: GradeAverageMode config: AverageCalcParams,
): Flow<Resource<List<GradeSubject>>> { ): Flow<Resource<List<GradeSubject>>> {
val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
val selectedSemester = semesters.single { it.semesterId == semesterId } val selectedSemester = semesters.single { it.semesterId == semesterId }
val firstSemester = val firstSemester =
semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
val selectedSemesterGradeSubjects = val selectedSemesterGradeSubjects =
getGradeSubjects(student, selectedSemester, forceRefresh) getGradeSubjects(student, selectedSemester, forceRefresh, config)
if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects
val firstSemesterGradeSubjects = getGradeSubjects(student, firstSemester, forceRefresh) val firstSemesterGradeSubjects =
getGradeSubjects(student, firstSemester, forceRefresh, config)
return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject -> return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject ->
if (firstSemesterGradeSubject.errorOrNull != null) { if (firstSemesterGradeSubject.errorOrNull != null) {
@ -91,21 +112,21 @@ class GradeAverageProvider @Inject constructor(
val firstSemesterSubject = firstSemesterGradeSubject.dataOrNull.orEmpty() val firstSemesterSubject = firstSemesterGradeSubject.dataOrNull.orEmpty()
.singleOrNull { it.subject == secondSemesterSubject.subject } .singleOrNull { it.subject == secondSemesterSubject.subject }
val updatedAverage = if (averageMode == ALL_YEAR) { val updatedAverage = if (config.gradeAverageMode == ALL_YEAR) {
calculateAllYearAverage( calculateAllYearAverage(
student = student, student = student,
isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester,
isGradeAverageForceCalc = isGradeAverageForceCalc,
secondSemesterSubject = secondSemesterSubject, secondSemesterSubject = secondSemesterSubject,
firstSemesterSubject = firstSemesterSubject firstSemesterSubject = firstSemesterSubject,
config = config,
) )
} else { } else {
calculateBothSemestersAverage( calculateBothSemestersAverage(
student = student, student = student,
isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester,
isGradeAverageForceCalc = isGradeAverageForceCalc,
secondSemesterSubject = secondSemesterSubject, secondSemesterSubject = secondSemesterSubject,
firstSemesterSubject = firstSemesterSubject firstSemesterSubject = firstSemesterSubject,
config = config
) )
} }
secondSemesterSubject.copy(average = updatedAverage) secondSemesterSubject.copy(average = updatedAverage)
@ -117,17 +138,17 @@ class GradeAverageProvider @Inject constructor(
private fun calculateAllYearAverage( private fun calculateAllYearAverage(
student: Student, student: Student,
isAnyVulcanAverage: Boolean, isAnyVulcanAverage: Boolean,
isGradeAverageForceCalc: Boolean,
secondSemesterSubject: GradeSubject, secondSemesterSubject: GradeSubject,
firstSemesterSubject: GradeSubject? firstSemesterSubject: GradeSubject?,
) = if (!isAnyVulcanAverage || isGradeAverageForceCalc) { config: AverageCalcParams,
val updatedSecondSemesterGrades = ) = if (!isAnyVulcanAverage || config.forceAverageCalc) {
secondSemesterSubject.grades.updateModifiers(student) val updatedSecondSemesterGrades = secondSemesterSubject.grades
val updatedFirstSemesterGrades = .updateModifiers(student, config)
firstSemesterSubject?.grades?.updateModifiers(student).orEmpty() val updatedFirstSemesterGrades = firstSemesterSubject?.grades
?.updateModifiers(student, config).orEmpty()
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage( (updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
isOptionalArithmeticAverage config.isOptionalArithmeticAverage
) )
} else { } else {
secondSemesterSubject.average secondSemesterSubject.average
@ -136,32 +157,35 @@ class GradeAverageProvider @Inject constructor(
private fun calculateBothSemestersAverage( private fun calculateBothSemestersAverage(
student: Student, student: Student,
isAnyVulcanAverage: Boolean, isAnyVulcanAverage: Boolean,
isGradeAverageForceCalc: Boolean,
secondSemesterSubject: GradeSubject, secondSemesterSubject: GradeSubject,
firstSemesterSubject: GradeSubject? firstSemesterSubject: GradeSubject?,
): Double = if (!isAnyVulcanAverage || isGradeAverageForceCalc) { config: AverageCalcParams,
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1 ): Double {
return if (!isAnyVulcanAverage || config.forceAverageCalc) {
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
val secondSemesterAverage = secondSemesterSubject.grades
.updateModifiers(student, config)
.calcAverage(config.isOptionalArithmeticAverage)
val firstSemesterAverage = firstSemesterSubject?.grades
?.updateModifiers(student, config)
?.calcAverage(config.isOptionalArithmeticAverage) ?: secondSemesterAverage
val secondSemesterAverage = secondSemesterSubject.grades.updateModifiers(student) (secondSemesterAverage + firstSemesterAverage) / divider
.calcAverage(isOptionalArithmeticAverage) } else {
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student) val divider = if (secondSemesterSubject.average > 0) 2 else 1
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
(secondSemesterAverage + firstSemesterAverage) / divider secondSemesterSubject.average.plus(
} else { (firstSemesterSubject?.average ?: secondSemesterSubject.average)
val divider = if (secondSemesterSubject.average > 0) 2 else 1 ) / divider
}
(secondSemesterSubject.average + (firstSemesterSubject?.average
?: secondSemesterSubject.average)) / divider
} }
private fun getGradeSubjects( private fun getGradeSubjects(
student: Student, student: Student,
semester: Semester, semester: Semester,
forceRefresh: Boolean forceRefresh: Boolean,
params: AverageCalcParams,
): Flow<Resource<List<GradeSubject>>> { ): Flow<Resource<List<GradeSubject>>> {
val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh) return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
.mapResourceData { res -> .mapResourceData { res ->
val (details, summaries) = res val (details, summaries) = res
@ -172,13 +196,15 @@ class GradeAverageProvider @Inject constructor(
student = student, student = student,
semester = semester, semester = semester,
grades = allGrades.toList(), grades = allGrades.toList(),
calcAverage = isAnyAverage calcAverage = isAnyAverage,
params = params,
).map { summary -> ).map { summary ->
val grades = allGrades[summary.subject].orEmpty() val grades = allGrades[summary.subject].orEmpty()
GradeSubject( GradeSubject(
subject = summary.subject, subject = summary.subject,
average = if (!isAnyAverage || isGradeAverageForceCalc) { average = if (!isAnyAverage || params.forceAverageCalc) {
grades.updateModifiers(student).calcAverage(isOptionalArithmeticAverage) grades.updateModifiers(student, params)
.calcAverage(params.isOptionalArithmeticAverage)
} else summary.average, } else summary.average,
points = summary.pointsSum, points = summary.pointsSum,
summary = summary, summary = summary,
@ -195,7 +221,8 @@ class GradeAverageProvider @Inject constructor(
student: Student, student: Student,
semester: Semester, semester: Semester,
grades: List<Pair<String, List<Grade>>>, grades: List<Pair<String, List<Grade>>>,
calcAverage: Boolean calcAverage: Boolean,
params: AverageCalcParams,
): List<GradeSummary> { ): List<GradeSummary> {
if (isNotEmpty() && size > grades.size) return this if (isNotEmpty() && size > grades.size) return this
@ -211,15 +238,16 @@ class GradeAverageProvider @Inject constructor(
proposedPoints = "", proposedPoints = "",
finalPoints = "", finalPoints = "",
pointsSum = "", pointsSum = "",
average = if (calcAverage) details.updateModifiers(student) average = if (calcAverage) details.updateModifiers(student, params)
.calcAverage(isOptionalArithmeticAverage) else .0 .calcAverage(params.isOptionalArithmeticAverage) else .0
) )
} }
} }
private fun List<Grade>.updateModifiers(student: Student): List<Grade> { private fun List<Grade>.updateModifiers(
return if (student.loginMode == Sdk.Mode.SCRAPPER.name) { student: Student,
map { it.changeModifier(plusModifier, minusModifier) } params: AverageCalcParams,
} else this ): List<Grade> = if (student.loginMode == Sdk.Mode.SCRAPPER.name) {
} map { it.changeModifier(params.plusModifier, params.minusModifier) }
} else this
} }