forked from github/wulkanowy-mirror
Fix endless loading in grades (#1084)
This commit is contained in:
parent
1a4eaec47f
commit
5d849b3ada
@ -137,10 +137,6 @@ ext {
|
||||
moshi = "1.11.0"
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.force "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:edc165bb"
|
||||
|
||||
@ -161,7 +157,7 @@ dependencies {
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.1"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
||||
implementation "com.google.android.material:material:1.2.1"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
|
||||
|
@ -1,7 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.grade
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
@ -18,10 +17,8 @@ import io.github.wulkanowy.utils.changeModifier
|
||||
import io.github.wulkanowy.utils.flowWithResourceIn
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -50,20 +47,20 @@ class GradeAverageProvider @Inject constructor(
|
||||
val selectedSemester = semesters.single { it.semesterId == semesterId }
|
||||
val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
|
||||
|
||||
return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).flatMapConcat { selectedDetails ->
|
||||
val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 }
|
||||
val selectedSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh)
|
||||
|
||||
if (selectedSemester != firstSemester) {
|
||||
getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails ->
|
||||
secondDetails.copy(data = selectedDetails.data?.map { selected ->
|
||||
val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject }
|
||||
selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||
val selectedGrades = selected.grades.updateModifiers(student).calcAverage()
|
||||
(selectedGrades + (second?.grades?.updateModifiers(student)?.calcAverage() ?: selectedGrades)) / 2
|
||||
} else (selected.average + (second?.average ?: selected.average)) / 2)
|
||||
})
|
||||
}.filter { it.status != Status.LOADING }.filter { it.data != null }
|
||||
} else flowOf(selectedDetails)
|
||||
return if (selectedSemester == firstSemester) selectedSemesterDetailsWithAverage else {
|
||||
val firstSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, firstSemester, forceRefresh)
|
||||
selectedSemesterDetailsWithAverage.combine(firstSemesterDetailsWithAverage) { selectedDetails, secondDetails ->
|
||||
val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 }
|
||||
secondDetails.copy(data = selectedDetails.data?.map { selected ->
|
||||
val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject }
|
||||
selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||
val selectedGrades = selected.grades.updateModifiers(student).calcAverage()
|
||||
(selectedGrades + (second?.grades?.updateModifiers(student)?.calcAverage() ?: selectedGrades)) / 2
|
||||
} else (selected.average + (second?.average ?: selected.average)) / 2)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,19 +68,19 @@ class GradeAverageProvider @Inject constructor(
|
||||
val selectedSemester = semesters.single { it.semesterId == semesterId }
|
||||
val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
|
||||
|
||||
return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).flatMapConcat { selectedDetails ->
|
||||
val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 }
|
||||
val selectedSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh)
|
||||
|
||||
if (selectedSemester != firstSemester) {
|
||||
getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails ->
|
||||
secondDetails.copy(data = selectedDetails.data?.map { selected ->
|
||||
val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject }
|
||||
selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||
(selected.grades.updateModifiers(student) + second?.grades?.updateModifiers(student).orEmpty()).calcAverage()
|
||||
} else selected.average)
|
||||
})
|
||||
}.filter { it.status != Status.LOADING }.filter { it.data != null }
|
||||
} else flowOf(selectedDetails)
|
||||
return if (selectedSemester == firstSemester) selectedSemesterDetailsWithAverage else {
|
||||
val firstSemesterDetailsWithAverage = getSemesterDetailsWithAverage(student, firstSemester, forceRefresh)
|
||||
selectedSemesterDetailsWithAverage.combine(firstSemesterDetailsWithAverage) { selectedDetails, secondDetails ->
|
||||
val isAnyAverage = selectedDetails.data.orEmpty().any { it.average != .0 }
|
||||
secondDetails.copy(data = selectedDetails.data?.map { selected ->
|
||||
val second = secondDetails.data.orEmpty().singleOrNull { it.subject == selected.subject }
|
||||
selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||
(selected.grades.updateModifiers(student) + second?.grades?.updateModifiers(student).orEmpty()).calcAverage()
|
||||
} else selected.average)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +90,7 @@ class GradeAverageProvider @Inject constructor(
|
||||
val isAnyAverage = summaries.orEmpty().any { it.average != .0 }
|
||||
val allGrades = details.orEmpty().groupBy { it.subject }
|
||||
|
||||
Resource(res.status, summaries?.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage)?.map { summary ->
|
||||
val items = summaries?.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage)?.map { summary ->
|
||||
val grades = allGrades[summary.subject].orEmpty()
|
||||
GradeDetailsWithAverage(
|
||||
subject = summary.subject,
|
||||
@ -104,7 +101,9 @@ class GradeAverageProvider @Inject constructor(
|
||||
summary = summary,
|
||||
grades = grades
|
||||
)
|
||||
}, res.error)
|
||||
}
|
||||
|
||||
Resource(res.status, items, res.error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,16 +143,18 @@ class GradeDetailsPresenter @Inject constructor(
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh)
|
||||
}.onEach {
|
||||
Timber.d("Loading grade details status: ${it.status}, data: ${it.data != null}")
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
val items = createGradeItems(it.data.orEmpty())
|
||||
if (items.isNotEmpty()) {
|
||||
Timber.i("Loading gradle details result: load cached data")
|
||||
Timber.i("Loading grade details result: load cached data")
|
||||
view?.run {
|
||||
updateNewGradesAmount(it.data.orEmpty())
|
||||
enableSwipe(true)
|
||||
showRefresh(true)
|
||||
showProgress(false)
|
||||
showEmpty(false)
|
||||
showContent(true)
|
||||
updateData(
|
||||
data = items,
|
||||
|
@ -43,6 +43,7 @@ class GradeSummaryPresenter @Inject constructor(
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh)
|
||||
}.onEach {
|
||||
Timber.d("Loading grade summary status: ${it.status}, data: ${it.data != null}")
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
val items = createGradeSummaryItems(it.data.orEmpty())
|
||||
@ -52,6 +53,7 @@ class GradeSummaryPresenter @Inject constructor(
|
||||
enableSwipe(true)
|
||||
showRefresh(true)
|
||||
showProgress(false)
|
||||
showEmpty(false)
|
||||
showContent(true)
|
||||
updateData(items)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.ui.modules.grade
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
@ -15,6 +16,7 @@ import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
@ -99,13 +101,42 @@ class GradeAverageProviderTest {
|
||||
gradeAverageProvider = GradeAverageProvider(semesterRepository, gradeRepository, preferencesRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calc current semester average with load from cache sequence`() {
|
||||
every { preferencesRepository.gradeAverageForceCalc } returns true
|
||||
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER
|
||||
coEvery { semesterRepository.getSemesters(student) } returns semesters
|
||||
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow {
|
||||
emit(Resource.loading())
|
||||
emit(Resource.loading(secondGradeWithModifier to secondSummariesWithModifier))
|
||||
emit(Resource.success(secondGradeWithModifier to secondSummariesWithModifier))
|
||||
}
|
||||
|
||||
val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() }
|
||||
|
||||
with(items[0]) {
|
||||
assertEquals(Status.LOADING, status)
|
||||
assertEquals(null, data)
|
||||
}
|
||||
with(items[1]) {
|
||||
assertEquals(Status.LOADING, status)
|
||||
assertEquals(1, data!!.size)
|
||||
}
|
||||
with(items[2]) {
|
||||
assertEquals(Status.SUCCESS, status)
|
||||
assertEquals(1, data!!.size)
|
||||
}
|
||||
|
||||
assertEquals(3.5, items[1].data?.single { it.subject == "Język polski" }!!.average, .0) // from details and after set custom plus/minus
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `force calc average on no grades`() {
|
||||
every { preferencesRepository.gradeAverageForceCalc } returns true
|
||||
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS
|
||||
|
||||
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { emptyList<Grade>() to emptyList<GradeSummary>() }
|
||||
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { emptyList<Grade>() to emptyList<GradeSummary>() }
|
||||
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { emptyList<Grade>() to emptyList() }
|
||||
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { emptyList<Grade>() to emptyList() }
|
||||
|
||||
val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() }
|
||||
|
||||
@ -214,6 +245,37 @@ class GradeAverageProviderTest {
|
||||
assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calc full year average when current is first with load from cache sequence`() {
|
||||
every { preferencesRepository.gradeAverageForceCalc } returns true
|
||||
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR
|
||||
coEvery { semesterRepository.getSemesters(student) } returns semesters
|
||||
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow {
|
||||
emit(Resource.loading())
|
||||
emit(Resource.loading(firstGrades to firstSummaries))
|
||||
emit(Resource.success(firstGrades to firstSummaries))
|
||||
}
|
||||
|
||||
val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId, true).toList() }
|
||||
|
||||
with(items[0]) {
|
||||
assertEquals(Status.LOADING, status)
|
||||
assertEquals(null, data)
|
||||
}
|
||||
with(items[1]) {
|
||||
assertEquals(Status.LOADING, status)
|
||||
assertEquals(2, data!!.size)
|
||||
}
|
||||
with(items[2]) {
|
||||
assertEquals(Status.SUCCESS, status)
|
||||
assertEquals(2, data!!.size)
|
||||
}
|
||||
|
||||
assertEquals(2, items[2].data!!.size)
|
||||
assertEquals(3.5, items[2].data!!.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5
|
||||
assertEquals(3.5, items[2].data!!.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calc both semesters average`() {
|
||||
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS
|
||||
@ -238,6 +300,53 @@ class GradeAverageProviderTest {
|
||||
assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calc both semesters average when current is second with load from cache sequence`() {
|
||||
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS
|
||||
every { preferencesRepository.gradeAverageForceCalc } returns false
|
||||
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow {
|
||||
emit(Resource.loading())
|
||||
emit(Resource.loading(firstGrades to listOf(
|
||||
getSummary(22, "Matematyka", 3.0),
|
||||
getSummary(22, "Fizyka", 3.5)
|
||||
)))
|
||||
emit(Resource.success(firstGrades to listOf(
|
||||
getSummary(22, "Matematyka", 3.0),
|
||||
getSummary(22, "Fizyka", 3.5)
|
||||
)))
|
||||
}
|
||||
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow {
|
||||
emit(Resource.loading())
|
||||
emit(Resource.loading(secondGrades to listOf(
|
||||
getSummary(22, "Matematyka", 3.5),
|
||||
getSummary(22, "Fizyka", 4.0)
|
||||
)))
|
||||
emit(Resource.success(secondGrades to listOf(
|
||||
getSummary(22, "Matematyka", 3.5),
|
||||
getSummary(22, "Fizyka", 4.0)
|
||||
)))
|
||||
}
|
||||
|
||||
val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() }
|
||||
|
||||
with(items[0]) {
|
||||
assertEquals(Status.LOADING, status)
|
||||
assertEquals(null, data)
|
||||
}
|
||||
with(items[1]) {
|
||||
assertEquals(Status.LOADING, status)
|
||||
assertEquals(2, data!!.size)
|
||||
}
|
||||
with(items[2]) {
|
||||
assertEquals(Status.SUCCESS, status)
|
||||
assertEquals(2, data!!.size)
|
||||
}
|
||||
|
||||
assertEquals(2, items[2].data!!.size)
|
||||
assertEquals(3.25, items[2].data!!.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25
|
||||
assertEquals(3.75, items[2].data!!.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `force calc full year average`() {
|
||||
every { preferencesRepository.gradeAverageForceCalc } returns true
|
||||
@ -257,13 +366,55 @@ class GradeAverageProviderTest {
|
||||
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calc full year average when current is second with load from cache sequence`() {
|
||||
every { preferencesRepository.gradeAverageForceCalc } returns true
|
||||
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR
|
||||
coEvery { semesterRepository.getSemesters(student) } returns semesters
|
||||
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow {
|
||||
emit(Resource.loading())
|
||||
emit(Resource.loading(firstGrades to firstSummaries))
|
||||
emit(Resource.success(firstGrades to firstSummaries))
|
||||
}
|
||||
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow {
|
||||
emit(Resource.loading())
|
||||
emit(Resource.loading(secondGrades to listOf(
|
||||
getSummary(22, "Matematyka", 1.1),
|
||||
getSummary(22, "Fizyka", 7.26)
|
||||
)))
|
||||
emit(Resource.success(secondGrades to listOf(
|
||||
getSummary(22, "Matematyka", 1.1),
|
||||
getSummary(22, "Fizyka", 7.26)
|
||||
)))
|
||||
}
|
||||
|
||||
val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() }
|
||||
|
||||
with(items[0]) {
|
||||
assertEquals(Status.LOADING, status)
|
||||
assertEquals(null, data)
|
||||
}
|
||||
with(items[1]) {
|
||||
assertEquals(Status.LOADING, status)
|
||||
assertEquals(2, data!!.size)
|
||||
}
|
||||
with(items[2]) {
|
||||
assertEquals(Status.SUCCESS, status)
|
||||
assertEquals(2, data!!.size)
|
||||
}
|
||||
|
||||
assertEquals(2, items[2].data!!.size)
|
||||
assertEquals(3.0, items[2].data!!.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
|
||||
assertEquals(3.25, items[2].data!!.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calc both semesters average when no summaries`() {
|
||||
every { preferencesRepository.gradeAverageForceCalc } returns true
|
||||
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS
|
||||
|
||||
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to emptyList<GradeSummary>() }
|
||||
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to emptyList<GradeSummary>() }
|
||||
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to emptyList() }
|
||||
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to emptyList() }
|
||||
|
||||
val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() }
|
||||
|
||||
@ -277,8 +428,8 @@ class GradeAverageProviderTest {
|
||||
every { preferencesRepository.gradeAverageForceCalc } returns true
|
||||
every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR
|
||||
|
||||
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to emptyList<GradeSummary>() }
|
||||
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to emptyList<GradeSummary>() }
|
||||
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to emptyList() }
|
||||
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to emptyList() }
|
||||
|
||||
val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user