Show grades average from register if exist instead of calculating (#374)

This commit is contained in:
Mikołaj Pich 2019-05-31 14:40:53 +02:00 committed by Rafał Borcz
parent 383cab4dae
commit 0f75ff3206
15 changed files with 1607 additions and 60 deletions

View File

@ -85,7 +85,7 @@ play {
}
dependencies {
implementation 'io.github.wulkanowy:api:0.8.4'
implementation 'com.github.wulkanowy:api:c84356f'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.legacy:legacy-support-v4:1.0.0"

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,11 @@ abstract class AbstractMigrationTest {
fun getMigratedRoomDatabase(): AppDatabase {
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, dbName)
.addMigrations(Migration12(), Migration13())
.addMigrations(
Migration12(),
Migration13(),
Migration14()
)
.build()
// close the database and release any stream resources when the test finishes
helper.closeWhenFinished(database)

View File

@ -44,6 +44,7 @@ import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11
import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration13
import io.github.wulkanowy.data.db.migrations.Migration14
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4
@ -82,7 +83,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 13
const val VERSION_SCHEMA = 14
fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
@ -101,7 +102,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration10(),
Migration11(),
Migration12(),
Migration13()
Migration13(),
Migration14()
)
.build()
}

View File

@ -13,11 +13,26 @@ data class GradeSummary(
@ColumnInfo(name = "student_id")
val studentId: Int,
val position: Int,
val subject: String,
@ColumnInfo(name = "predicted_grade")
val predictedGrade: String,
val finalGrade: String
@ColumnInfo(name = "final_grade")
val finalGrade: String,
@ColumnInfo(name = "proposed_points")
val proposedPoints: String,
@ColumnInfo(name = "final_points")
val finalPoints: String,
@ColumnInfo(name = "points_sum")
val pointsSum: String,
val average: Double
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0

View File

@ -0,0 +1,26 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration14 : Migration(13, 14) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS GradesSummary")
database.execSQL("""
CREATE TABLE IF NOT EXISTS GradesSummary (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
semester_id INTEGER NOT NULL,
student_id INTEGER NOT NULL,
position INTEGER NOT NULL,
subject TEXT NOT NULL,
predicted_grade TEXT NOT NULL,
final_grade TEXT NOT NULL,
proposed_points TEXT NOT NULL,
final_points TEXT NOT NULL,
points_sum TEXT NOT NULL,
average REAL NOT NULL
)
""")
}
}

View File

@ -18,9 +18,14 @@ class GradeSummaryRemote @Inject constructor(private val api: Api) {
GradeSummary(
semesterId = semester.semesterId,
studentId = semester.studentId,
position = it.order,
subject = it.name,
predictedGrade = it.predicted,
finalGrade = it.final
finalGrade = it.final,
pointsSum = it.pointsSum,
proposedPoints = it.proposedPoints,
finalPoints = it.finalPoints,
average = it.average
)
}
}

View File

@ -3,16 +3,20 @@ package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier
import io.reactivex.Maybe
import io.reactivex.Single
import javax.inject.Inject
class GradeAverageProvider @Inject constructor(
private val preferencesRepository: PreferencesRepository,
private val gradeRepository: GradeRepository
private val gradeRepository: GradeRepository,
private val gradeSummaryRepository: GradeSummaryRepository
) {
fun getGradeAverage(student: Student, semesters: List<Semester>, selectedSemesterId: Int, forceRefresh: Boolean): Single<Map<String, Double>> {
return when (preferencesRepository.gradeAverageMode) {
"all_year" -> getAllYearAverage(student, semesters, selectedSemesterId, forceRefresh)
@ -27,16 +31,19 @@ class GradeAverageProvider @Inject constructor(
val plusModifier = preferencesRepository.gradePlusModifier
val minusModifier = preferencesRepository.gradeMinusModifier
return gradeRepository.getGrades(student, selectedSemester, forceRefresh)
.flatMap { firstGrades ->
if (selectedSemester == firstSemester) Single.just(firstGrades)
else gradeRepository.getGrades(student, firstSemester)
.map { secondGrades -> secondGrades + firstGrades }
}.map { grades ->
grades.map { it.changeModifier(plusModifier, minusModifier) }
.groupBy { it.subject }
.mapValues { it.value.calcAverage() }
}
return getAverageFromGradeSummary(selectedSemester, forceRefresh)
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
.flatMap { firstGrades ->
if (selectedSemester == firstSemester) Single.just(firstGrades)
else {
gradeRepository.getGrades(student, firstSemester)
.map { secondGrades -> secondGrades + firstGrades }
}
}.map { grades ->
grades.map { it.changeModifier(plusModifier, minusModifier) }
.groupBy { it.subject }
.mapValues { it.value.calcAverage() }
})
}
private fun getOnlyOneSemesterAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<Map<String, Double>> {
@ -44,11 +51,22 @@ class GradeAverageProvider @Inject constructor(
val plusModifier = preferencesRepository.gradePlusModifier
val minusModifier = preferencesRepository.gradeMinusModifier
return gradeRepository.getGrades(student, selectedSemester, forceRefresh)
.map { grades ->
grades.map { it.changeModifier(plusModifier, minusModifier) }
.groupBy { it.subject }
.mapValues { it.value.calcAverage() }
return getAverageFromGradeSummary(selectedSemester, forceRefresh)
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
.map { grades ->
grades.map { it.changeModifier(plusModifier, minusModifier) }
.groupBy { it.subject }
.mapValues { it.value.calcAverage() }
})
}
private fun getAverageFromGradeSummary(selectedSemester: Semester, forceRefresh: Boolean): Maybe<Map<String, Double>> {
return gradeSummaryRepository.getGradesSummary(selectedSemester, forceRefresh)
.toMaybe()
.flatMap {
if (it.any { summary -> summary.average != .0 }) {
Maybe.just(it.map { summary -> summary.subject to summary.average }.toMap())
} else Maybe.empty()
}
}
}

View File

@ -1,19 +1,21 @@
package io.github.wulkanowy.ui.modules.grade.summary
import android.annotation.SuppressLint
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradeSummary
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_grade_summary.*
class GradeSummaryItem(
private val title: String,
private val average: String,
private val predictedGrade: String,
private val finalGrade: String
val summary: GradeSummary,
private val average: String
) : AbstractFlexibleItem<GradeSummaryItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_grade_summary
@ -22,12 +24,16 @@ class GradeSummaryItem(
return ViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.run {
gradeSummaryItemTitle.text = title
gradeSummaryItemTitle.text = summary.subject
gradeSummaryItemPoints.text = summary.pointsSum
gradeSummaryItemAverage.text = average
gradeSummaryItemPredicted.text = predictedGrade
gradeSummaryItemFinal.text = finalGrade
gradeSummaryItemPredicted.text = "${summary.predictedGrade} ${summary.proposedPoints}".trim()
gradeSummaryItemFinal.text = "${summary.finalGrade} ${summary.finalPoints}".trim()
gradeSummaryItemPointsContainer.visibility = if (summary.pointsSum.isBlank()) GONE else VISIBLE
}
}
@ -38,18 +44,16 @@ class GradeSummaryItem(
other as GradeSummaryItem
if (average != other.average) return false
if (title != other.title) return false
if (predictedGrade != other.predictedGrade) return false
if (finalGrade != other.finalGrade) return false
if (summary != other.summary) return false
if (summary.id != other.summary.id) return false
return true
}
override fun hashCode(): Int {
var result = title.hashCode()
var result = summary.hashCode()
result = 31 * result + summary.id.hashCode()
result = 31 * result + average.hashCode()
result = 31 * result + predictedGrade.hashCode()
result = 31 * result + finalGrade.hashCode()
return result
}

View File

@ -96,10 +96,8 @@ class GradeSummaryPresenter @Inject constructor(
gradesSummary.filter { !checkEmpty(it, filteredAverages) }
.map {
GradeSummaryItem(
title = it.subject,
average = formatAverage(filteredAverages.getOrElse(it.subject) { 0.0 }, ""),
predictedGrade = it.predictedGrade,
finalGrade = it.finalGrade
summary = it,
average = formatAverage(filteredAverages.getOrElse(it.subject) { 0.0 }, "")
)
}.let {
it to GradeSummaryScrollableHeader(

View File

@ -24,7 +24,7 @@
android:layout_marginRight="40dp"
android:layout_weight="1"
android:textSize="17sp"
tools:text="@tools:sample/lorem" />
tools:text="Fizyka" />
<TextView
android:id="@+id/gradeSummaryItemAverage"
@ -32,7 +32,39 @@
android:layout_height="wrap_content"
android:gravity="end"
android:textSize="12sp"
tools:text="@string/aboutlibrary_lib_version" />
tools:text="4.74" />
</LinearLayout>
<LinearLayout
android:id="@+id/gradeSummaryItemPointsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:minHeight="35dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_weight="1"
android:text="@string/grade_summary_points"
android:textSize="14sp" />
<TextView
android:id="@+id/gradeSummaryItemPoints"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:textSize="12sp"
tools:text="123/150" />
</LinearLayout>
<LinearLayout
@ -63,7 +95,7 @@
android:layout_marginRight="25dp"
android:gravity="end"
android:textSize="12sp"
tools:text="@string/aboutlibrary_lib_version" />
tools:text="dobry" />
</LinearLayout>
<LinearLayout
@ -94,6 +126,6 @@
android:layout_marginRight="25dp"
android:gravity="end"
android:textSize="12sp"
tools:text="@string/aboutlibrary_lib_version" />
tools:text="bardzo dobry" />
</LinearLayout>
</LinearLayout>

View File

@ -57,6 +57,7 @@
<string name="grade_no_average">Brak średniej</string>
<string name="grade_predicted">Przewidywana: %1$s</string>
<string name="grade_final">Końcowa: %1$s</string>
<string name="grade_summary_points">Suma punktów</string>
<string name="grade_summary_final_grade">Ocena końcowa</string>
<string name="grade_summary_predicted_grade">Przewidywana ocena</string>
<string name="grade_summary_calculated_average">Obliczona średnia</string>

View File

@ -57,6 +57,7 @@
<string name="grade_no_average">No average</string>
<string name="grade_predicted">Predicted: %1$s</string>
<string name="grade_final">Final: %1$s</string>
<string name="grade_summary_points">Total points</string>
<string name="grade_summary_final_grade">Final grade</string>
<string name="grade_summary_predicted_grade">Predicted grade</string>
<string name="grade_summary_calculated_average">Calculated average</string>

View File

@ -1,9 +1,11 @@
package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.reactivex.Single
import org.junit.Assert.assertEquals
@ -23,6 +25,9 @@ class GradeAverageProviderTest {
@Mock
lateinit var gradeRepository: GradeRepository
@Mock
lateinit var gradeSummaryRepository: GradeSummaryRepository
private lateinit var gradeAverageProvider: GradeAverageProvider
private val student = Student("", "", "", "", "", 101, "", "", "", "", 1, true, LocalDateTime.now())
@ -34,23 +39,23 @@ class GradeAverageProviderTest {
)
private val firstGrades = listOf(
getGrade(101, 22, "Matematyka", 4, .0, 1.0),
getGrade(101, 22, "Matematyka", 3, .0, 1.0),
getGrade(101, 22, "Fizyka", 6, .0, 1.0),
getGrade(101, 22, "Fizyka", 1, .0, 1.0)
getGrade(22, "Matematyka", 4),
getGrade(22, "Matematyka", 3),
getGrade(22, "Fizyka", 6),
getGrade(22, "Fizyka", 1)
)
private val secondGrade = listOf(
getGrade(101, 23, "Matematyka", 2, .0, 1.0),
getGrade(101, 23, "Matematyka", 3, .0, 1.0),
getGrade(101, 23, "Fizyka", 4, .0, 1.0),
getGrade(101, 23, "Fizyka", 2, .0, 1.0)
getGrade(23, "Matematyka", 2),
getGrade(23, "Matematyka", 3),
getGrade(23, "Fizyka", 4),
getGrade(23, "Fizyka", 2)
)
@Before
fun initTest() {
MockitoAnnotations.initMocks(this)
gradeAverageProvider = GradeAverageProvider(preferencesRepository, gradeRepository)
gradeAverageProvider = GradeAverageProvider(preferencesRepository, gradeRepository, gradeSummaryRepository)
doReturn(.33).`when`(preferencesRepository).gradeMinusModifier
doReturn(.33).`when`(preferencesRepository).gradePlusModifier
@ -62,6 +67,7 @@ class GradeAverageProviderTest {
@Test
fun onlyOneSemesterTest() {
doReturn("only_one_semester").`when`(preferencesRepository).gradeAverageMode
doReturn(Single.just(emptyList<GradeSummary>())).`when`(gradeSummaryRepository).getGradesSummary(semesters[2], true)
val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true)
.blockingGet()
@ -74,6 +80,7 @@ class GradeAverageProviderTest {
@Test
fun allYearFirstSemesterTest() {
doReturn("all_year").`when`(preferencesRepository).gradeAverageMode
doReturn(Single.just(emptyList<GradeSummary>())).`when`(gradeSummaryRepository).getGradesSummary(semesters[1], true)
val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[1].semesterId, true)
.blockingGet()
@ -87,6 +94,7 @@ class GradeAverageProviderTest {
fun allYearSecondSemesterTest() {
doReturn("all_year").`when`(preferencesRepository).gradeAverageMode
doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], false)
doReturn(Single.just(emptyList<GradeSummary>())).`when`(gradeSummaryRepository).getGradesSummary(semesters[2], true)
val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true)
.blockingGet()
@ -103,14 +111,31 @@ class GradeAverageProviderTest {
gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true).blockingGet()
}
private fun getGrade(studentId: Int, semesterId: Int, subject: String, value: Int, modifier: Double, weight: Double): Grade {
@Test
fun onlyOneSemester_averageFromSummary() {
doReturn("all_year").`when`(preferencesRepository).gradeAverageMode
doReturn(Single.just(firstGrades)).`when`(gradeRepository).getGrades(student, semesters[1], false)
doReturn(Single.just(listOf(
getSummary(22, "Matematyka", 3.1),
getSummary(22, "Fizyka", 3.26)
))).`when`(gradeSummaryRepository).getGradesSummary(semesters[2], true)
val averages = gradeAverageProvider.getGradeAverage(student, semesters, semesters[2].semesterId, true)
.blockingGet()
assertEquals(2, averages.size)
assertEquals(3.1, averages["Matematyka"])
assertEquals(3.26, averages["Fizyka"])
}
private fun getGrade(semesterId: Int, subject: String, value: Int): Grade {
return Grade(
studentId = studentId,
studentId = 101,
semesterId = semesterId,
subject = subject,
value = value,
modifier = modifier,
weightValue = weight,
modifier = .0,
weightValue = 1.0,
teacher = "",
date = now(),
weight = "",
@ -121,4 +146,19 @@ class GradeAverageProviderTest {
color = ""
)
}
private fun getSummary(semesterId: Int, subject: String, value: Double): GradeSummary {
return GradeSummary(
studentId = 101,
semesterId = semesterId,
subject = subject,
average = value,
pointsSum = "",
proposedPoints = "",
finalPoints = "",
finalGrade = "",
predictedGrade = "",
position = 0
)
}
}

View File

@ -33,10 +33,10 @@ class GradeExtensionTest {
@Test
fun calcSummaryAverage() {
assertEquals(2.5, listOf(
GradeSummary(1, 1, "", "", "5"),
GradeSummary(1, 1, "", "", "-5"),
GradeSummary(1, 1, "", "", "test"),
GradeSummary(1, 1, "", "", "0")
createGradeSummary("5"),
createGradeSummary("-5"),
createGradeSummary("test"),
createGradeSummary("0")
).calcAverage(), 0.005)
}
@ -76,4 +76,19 @@ class GradeExtensionTest {
teacher = ""
)
}
private fun createGradeSummary(finalGrade: String): GradeSummary {
return GradeSummary(
semesterId = 1,
studentId = 1,
position = 0,
subject = "",
predictedGrade = "",
finalGrade = finalGrade,
proposedPoints = "",
finalPoints = "",
pointsSum = "",
average = .0
)
}
}