Fix endless loading in grades (#1084)

This commit is contained in:
Rafał Borcz 2021-01-20 17:42:19 +01:00 committed by GitHub
parent 1a4eaec47f
commit 5d849b3ada
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 193 additions and 43 deletions

View File

@ -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"

View File

@ -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,11 +47,12 @@ 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 ->
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) {
@ -62,8 +60,7 @@ class GradeAverageProvider @Inject constructor(
(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)
}
}
}
@ -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 ->
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)
})
}.filter { it.status != Status.LOADING }.filter { it.data != null }
} else flowOf(selectedDetails)
}
}
}
@ -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)
}
}

View File

@ -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,

View File

@ -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)
}

View File

@ -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() }