Add predicted and final grade notifications (#872)

This commit is contained in:
Dominik Korsa 2020-06-13 17:11:18 +02:00 committed by GitHub
parent a529836937
commit a6682c9b73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1927 additions and 12 deletions

File diff suppressed because it is too large Load Diff

View File

@ -68,6 +68,7 @@ import io.github.wulkanowy.data.db.migrations.Migration22
import io.github.wulkanowy.data.db.migrations.Migration23 import io.github.wulkanowy.data.db.migrations.Migration23
import io.github.wulkanowy.data.db.migrations.Migration24 import io.github.wulkanowy.data.db.migrations.Migration24
import io.github.wulkanowy.data.db.migrations.Migration25 import io.github.wulkanowy.data.db.migrations.Migration25
import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration5
@ -110,7 +111,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 25 const val VERSION_SCHEMA = 26
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> { fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
return arrayOf( return arrayOf(
@ -137,7 +138,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration22(), Migration22(),
Migration23(), Migration23(),
Migration24(), Migration24(),
Migration25() Migration25(),
Migration26()
) )
} }

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import org.threeten.bp.LocalDateTime
@Entity(tableName = "GradesSummary") @Entity(tableName = "GradesSummary")
data class GradeSummary( data class GradeSummary(
@ -36,4 +37,16 @@ data class GradeSummary(
) { ) {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
@ColumnInfo(name = "is_predicted_grade_notified")
var isPredictedGradeNotified: Boolean = true
@ColumnInfo(name = "is_final_grade_notified")
var isFinalGradeNotified: Boolean = true
@ColumnInfo(name = "predicted_grade_last_change")
var predictedGradeLastChange: LocalDateTime = LocalDateTime.now()
@ColumnInfo(name = "final_grade_last_change")
var finalGradeLastChange: LocalDateTime = LocalDateTime.now()
} }

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration26 : Migration(25, 26) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_predicted_grade_notified INTEGER NOT NULL DEFAULT 1")
database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_final_grade_notified INTEGER NOT NULL DEFAULT 1")
database.execSQL("ALTER TABLE GradesSummary ADD COLUMN predicted_grade_last_change INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE GradesSummary ADD COLUMN final_grade_last_change INTEGER NOT NULL DEFAULT 0")
}
}

View File

@ -27,6 +27,10 @@ class GradeLocal @Inject constructor(
gradeDb.updateAll(grades) gradeDb.updateAll(grades)
} }
fun updateGradesSummary(gradesSummary: List<GradeSummary>) {
gradeSummaryDb.updateAll(gradesSummary)
}
fun getGradesDetails(semester: Semester): Maybe<List<Grade>> { fun getGradesDetails(semester: Semester): Maybe<List<Grade>> {
return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() }
} }

View File

@ -9,6 +9,7 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDateTime
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -43,7 +44,31 @@ class GradeRepository @Inject constructor(
local.getGradesSummary(semester).toSingle(emptyList()) local.getGradesSummary(semester).toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteGradesSummary(old.uniqueSubtract(newSummary)) local.deleteGradesSummary(old.uniqueSubtract(newSummary))
local.saveGradesSummary(newSummary.uniqueSubtract(old)) local.saveGradesSummary(newSummary.uniqueSubtract(old)
.onEach { summary ->
val oldSummary = old.find { oldSummary -> oldSummary.subject == summary.subject }
summary.isPredictedGradeNotified = when {
summary.predictedGrade.isEmpty() -> true
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
else -> true
}
summary.isFinalGradeNotified = when {
summary.finalGrade.isEmpty() -> true
notify && oldSummary?.finalGrade != summary.finalGrade -> false
else -> true
}
summary.predictedGradeLastChange = when {
oldSummary == null -> LocalDateTime.now()
summary.predictedGrade != oldSummary.predictedGrade -> LocalDateTime.now()
else -> oldSummary.predictedGradeLastChange
}
summary.finalGradeLastChange = when {
oldSummary == null -> LocalDateTime.now()
summary.finalGrade != oldSummary.finalGrade -> LocalDateTime.now()
else -> oldSummary.finalGradeLastChange
}
})
} }
} }
}.flatMap { }.flatMap {
@ -63,6 +88,14 @@ class GradeRepository @Inject constructor(
return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList()) return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList())
} }
fun getNotNotifiedPredictedGrades(semester: Semester): Single<List<GradeSummary>> {
return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } }.toSingle(emptyList())
}
fun getNotNotifiedFinalGrades(semester: Semester): Single<List<GradeSummary>> {
return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } }.toSingle(emptyList())
}
fun updateGrade(grade: Grade): Completable { fun updateGrade(grade: Grade): Completable {
return Completable.fromCallable { local.updateGrades(listOf(grade)) } return Completable.fromCallable { local.updateGrades(listOf(grade)) }
} }
@ -70,4 +103,8 @@ class GradeRepository @Inject constructor(
fun updateGrades(grades: List<Grade>): Completable { fun updateGrades(grades: List<Grade>): Completable {
return Completable.fromCallable { local.updateGrades(grades) } return Completable.fromCallable { local.updateGrades(grades) }
} }
fun updateGradesSummary(gradesSummary: List<GradeSummary>): Completable {
return Completable.fromCallable { local.updateGradesSummary(gradesSummary) }
}
} }

View File

@ -9,6 +9,7 @@ import androidx.core.app.NotificationCompat.PRIORITY_HIGH
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.grade.GradeRepository
@ -30,17 +31,21 @@ class GradeWork @Inject constructor(
override fun create(student: Student, semester: Semester): Completable { override fun create(student: Student, semester: Semester): Completable {
return gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable) return gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable)
.flatMap { gradeRepository.getNotNotifiedGrades(semester) } .ignoreElement()
.flatMapCompletable { .concatWith(Completable.concatArray(gradeRepository.getNotNotifiedGrades(semester).flatMapCompletable {
if (it.isNotEmpty()) notify(it) if (it.isNotEmpty()) notifyDetails(it)
gradeRepository.updateGrades(it.onEach { grade -> grade.isNotified = true }) gradeRepository.updateGrades(it.onEach { grade -> grade.isNotified = true })
} }, gradeRepository.getNotNotifiedPredictedGrades(semester).flatMapCompletable {
if (it.isNotEmpty()) notifyPredicted(it)
gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isPredictedGradeNotified = true })
}, gradeRepository.getNotNotifiedFinalGrades(semester).flatMapCompletable {
if (it.isNotEmpty()) notifyFinal(it)
gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isFinalGradeNotified = true })
}))
} }
private fun notify(grades: List<Grade>) { private fun getNotificationBuilder(): NotificationCompat.Builder {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID) return NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size))
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size))
.setSmallIcon(R.drawable.ic_stat_grade) .setSmallIcon(R.drawable.ic_stat_grade)
.setAutoCancel(true) .setAutoCancel(true)
.setPriority(PRIORITY_HIGH) .setPriority(PRIORITY_HIGH)
@ -49,6 +54,12 @@ class GradeWork @Inject constructor(
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, MainView.Section.GRADE.id, PendingIntent.getActivity(context, MainView.Section.GRADE.id,
MainActivity.getStartIntent(context, MainView.Section.GRADE, true), FLAG_UPDATE_CURRENT)) MainActivity.getStartIntent(context, MainView.Section.GRADE, true), FLAG_UPDATE_CURRENT))
}
private fun notifyDetails(grades: List<Grade>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder()
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size))
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size))
.setStyle(NotificationCompat.InboxStyle().run { .setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size)) setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size))
grades.forEach { addLine("${it.subject}: ${it.entry}") } grades.forEach { addLine("${it.subject}: ${it.entry}") }
@ -57,4 +68,30 @@ class GradeWork @Inject constructor(
.build() .build()
) )
} }
private fun notifyPredicted(gradesSummary: List<GradeSummary>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder()
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items_predicted, gradesSummary.size, gradesSummary.size))
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items_predicted, gradesSummary.size, gradesSummary.size))
.setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, gradesSummary.size, gradesSummary.size))
gradesSummary.forEach { addLine("${it.subject}: ${it.predictedGrade}") }
this
})
.build()
)
}
private fun notifyFinal(gradesSummary: List<GradeSummary>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder()
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items_final, gradesSummary.size, gradesSummary.size))
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items_final, gradesSummary.size, gradesSummary.size))
.setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, gradesSummary.size, gradesSummary.size))
gradesSummary.forEach { addLine("${it.subject}: ${it.finalGrade}") }
this
})
.build()
)
}
} }

View File

@ -107,12 +107,36 @@
<item quantity="many">Nowe oceny</item> <item quantity="many">Nowe oceny</item>
<item quantity="other">Nowe oceny</item> <item quantity="other">Nowe oceny</item>
</plurals> </plurals>
<plurals name="grade_new_items_predicted">
<item quantity="one">Nowa ocena przewidywana</item>
<item quantity="few">Nowe oceny przewidywane</item>
<item quantity="many">Nowe oceny przewidywane</item>
<item quantity="other">Nowe oceny przewidywane</item>
</plurals>
<plurals name="grade_new_items_final">
<item quantity="one">Nowa ocena końcowa</item>
<item quantity="few">Nowe oceny końcowe</item>
<item quantity="many">Nowe oceny końcowe</item>
<item quantity="other">Nowe oceny końcowe</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Masz %1$d nową ocenę</item> <item quantity="one">Masz %1$d nową ocenę</item>
<item quantity="few">Masz %1$d nowe oceny</item> <item quantity="few">Masz %1$d nowe oceny</item>
<item quantity="many">Masz %1$d nowych ocen</item> <item quantity="many">Masz %1$d nowych ocen</item>
<item quantity="other">Masz %1$d nowych ocen</item> <item quantity="other">Masz %1$d nowych ocen</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_predicted">
<item quantity="one">Masz %1$d nową przewidywaną ocenę</item>
<item quantity="few">Masz %1$d nowe przewidywane oceny</item>
<item quantity="many">Masz %1$d nowych przewidywanych ocen</item>
<item quantity="other">Masz %1$d nowych przewidywanych ocen</item>
</plurals>
<plurals name="grade_notify_new_items_final">
<item quantity="one">Masz %1$d nową końcową ocenę</item>
<item quantity="few">Masz %1$d nowe końcowe oceny</item>
<item quantity="many">Masz %1$d nowych końcowych ocen</item>
<item quantity="other">Masz %1$d nowych końcowych ocen</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Lekcja</string> <string name="timetable_lesson">Lekcja</string>
<string name="timetable_room">Sala</string> <string name="timetable_room">Sala</string>

View File

@ -113,10 +113,26 @@
<item quantity="one">New grade</item> <item quantity="one">New grade</item>
<item quantity="other">New grades</item> <item quantity="other">New grades</item>
</plurals> </plurals>
<plurals name="grade_new_items_predicted">
<item quantity="one">New predicted grade</item>
<item quantity="other">New predicted grades</item>
</plurals>
<plurals name="grade_new_items_final">
<item quantity="one">New final grade</item>
<item quantity="other">New final grades</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">You received %1$d grade</item> <item quantity="one">You received %1$d grade</item>
<item quantity="other">You received %1$d grades</item> <item quantity="other">You received %1$d grades</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_predicted">
<item quantity="one">You received %1$d predicted grade</item>
<item quantity="other">You received %1$d predicted grades</item>
</plurals>
<plurals name="grade_notify_new_items_final">
<item quantity="one">You received %1$d final grade</item>
<item quantity="other">You received %1$d final grades</item>
</plurals>
<!--Timetable--> <!--Timetable-->