Add new notifications (#1243)

This commit is contained in:
Tomasz F
2021-04-04 16:15:07 +02:00
committed by GitHub
parent 6cb4ea4b0f
commit 7bc5219d81
14 changed files with 2504 additions and 21 deletions

View File

@ -86,6 +86,7 @@ import io.github.wulkanowy.data.db.migrations.Migration32
import io.github.wulkanowy.data.db.migrations.Migration33
import io.github.wulkanowy.data.db.migrations.Migration34
import io.github.wulkanowy.data.db.migrations.Migration35
import io.github.wulkanowy.data.db.migrations.Migration36
import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6
@ -132,7 +133,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 35
const val VERSION_SCHEMA = 36
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@ -168,7 +169,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration32(),
Migration33(),
Migration34(),
Migration35(appInfo)
Migration35(appInfo),
Migration36()
)
fun newInstance(

View File

@ -36,4 +36,7 @@ data class Exam(
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
}

View File

@ -37,4 +37,7 @@ data class Homework(
@ColumnInfo(name = "is_done")
var isDone: Boolean = false
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
}

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration36 : Migration(35, 36) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Exams ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
database.execSQL("ALTER TABLE Homework ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
}
}

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
@ -12,6 +13,8 @@ import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
import javax.inject.Inject
@ -28,20 +31,54 @@ class ExamRepository @Inject constructor(
private val cacheKey = "exam"
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
fun getExams(
student: Student,
semester: Semester,
start: LocalDate,
end: LocalDate,
forceRefresh: Boolean,
notify: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex,
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
query = { examDb.loadAll(semester.diaryId, semester.studentId, start.startExamsDay, start.endExamsDay) },
shouldFetch = {
it.isEmpty() || forceRefresh
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end))
},
query = {
examDb.loadAll(
diaryId = semester.diaryId,
studentId = semester.studentId,
from = start.startExamsDay,
end = start.endExamsDay
)
},
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getExams(start.startExamsDay, start.endExamsDay, semester.semesterId)
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
val examsToSave = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
}
examDb.deleteAll(old uniqueSubtract new)
examDb.insertAll(new uniqueSubtract old)
examDb.insertAll(examsToSave)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
},
filterResult = { it.filter { item -> item.date in start..end } }
)
fun getNotNotifiedExam(semester: Semester, start: LocalDate): Flow<List<Exam>> {
return examDb.loadAll(
diaryId = semester.diaryId,
studentId = semester.studentId,
from = start.startExamsDay,
end = start.endExamsDay
).map {
it.filter { exam -> !exam.isNotified }
}
}
suspend fun updateExam(exam: List<Exam>) = examDb.updateAll(exam)
}

View File

@ -13,6 +13,7 @@ import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
import javax.inject.Inject
@ -29,18 +30,39 @@ class HomeworkRepository @Inject constructor(
private val cacheKey = "homework"
fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
fun getHomework(
student: Student,
semester: Semester,
start: LocalDate,
end: LocalDate,
forceRefresh: Boolean,
notify: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex,
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
query = { homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday) },
shouldFetch = {
it.isEmpty() || forceRefresh ||
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end))
},
query = {
homeworkDb.loadAll(
semesterId = semester.semesterId,
studentId = semester.studentId,
from = start.monday,
end = end.sunday
)
},
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getHomework(start.monday, end.sunday)
.mapToEntities(semester)
},
saveFetchResult = { old, new ->
val homeWorkToSave = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
}
homeworkDb.deleteAll(old uniqueSubtract new)
homeworkDb.insertAll(new uniqueSubtract old)
homeworkDb.insertAll(homeWorkToSave)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
}
@ -51,4 +73,15 @@ class HomeworkRepository @Inject constructor(
isDone = !isDone
}))
}
}
fun getNotNotifiedHomework(
semester: Semester,
start: LocalDate,
end: LocalDate
) = homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday)
.map {
it.filter { homework -> !homework.isNotified }
}
suspend fun updateHomework(homework: List<Homework>) = homeworkDb.updateAll(homework)
}

View File

@ -15,7 +15,9 @@ import dagger.multibindings.IntoSet
import io.github.wulkanowy.services.sync.channels.Channel
import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
import io.github.wulkanowy.services.sync.channels.NewExamChannel
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
import io.github.wulkanowy.services.sync.channels.PushChannel
@ -115,6 +117,14 @@ abstract class ServicesModule {
@IntoSet
abstract fun provideLuckyNumberChannel(channel: LuckyNumberChannel): Channel
@Binds
@IntoSet
abstract fun provideNewExamChannel(channel: NewExamChannel): Channel
@Binds
@IntoSet
abstract fun provideNewHomeworkChannel(channel: NewHomeworkChannel): Channel
@Binds
@IntoSet
abstract fun provideNewGradesChannel(channel: NewGradesChannel): Channel

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class NewExamChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
@ApplicationContext private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "new_exam_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_exam), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class NewHomeworkChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
@ApplicationContext private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "new_homework_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_homework), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -1,15 +1,85 @@
package io.github.wulkanowy.services.sync.works
import android.app.PendingIntent
import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewExamChannel
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first
import java.time.LocalDate.now
import javax.inject.Inject
import kotlin.random.Random
class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work {
class ExamWork @Inject constructor(
@ApplicationContext private val context: Context,
private val examRepository: ExamRepository,
private val notificationManager: NotificationManagerCompat,
private val preferencesRepository: PreferencesRepository
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
examRepository.getExams(student, semester, now(), now(), true).waitForResult()
examRepository.getExams(
student = student,
semester = semester,
start = now(),
end = now(),
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
).waitForResult()
examRepository.getNotNotifiedExam(semester, now()).first().let {
if (it.isNotEmpty()) notify(it)
examRepository.updateExam(it.onEach { exam -> exam.isNotified = true })
}
}
private fun notify(exam: List<Exam>) {
notificationManager.notify(
Random.nextInt(Int.MAX_VALUE),
NotificationCompat.Builder(context, NewExamChannel.CHANNEL_ID)
.setContentTitle(
context.resources.getQuantityString(
R.plurals.exam_notify_new_item_title,
exam.size,
exam.size
)
)
.setSmallIcon(R.drawable.ic_main_exam)
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent(
PendingIntent.getActivity(
context, MainView.Section.MESSAGE.id,
MainActivity.getStartIntent(context, MainView.Section.EXAM, true),
PendingIntent.FLAG_UPDATE_CURRENT
)
)
.setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(
context.resources.getQuantityString(
R.plurals.exam_number_item,
exam.size,
exam.size
)
)
exam.forEach { addLine("${it.subject}: ${it.description}") }
this
})
.build()
)
}
}

View File

@ -1,17 +1,90 @@
package io.github.wulkanowy.services.sync.works
import android.app.PendingIntent
import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first
import java.time.LocalDate.now
import javax.inject.Inject
import kotlin.random.Random
class HomeworkWork @Inject constructor(private val homeworkRepository: HomeworkRepository) : Work {
class HomeworkWork @Inject constructor(
@ApplicationContext private val context: Context,
private val homeworkRepository: HomeworkRepository,
private val notificationManager: NotificationManagerCompat,
private val preferencesRepository: PreferencesRepository
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
homeworkRepository.getHomework(student, semester, now().monday, now().sunday, true).waitForResult()
homeworkRepository.getHomework(
student = student,
semester = semester,
start = now().monday,
end = now().sunday,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
).waitForResult()
homeworkRepository.getNotNotifiedHomework(semester, now().monday, now().sunday).first()
.let {
if (it.isNotEmpty()) notify(it)
homeworkRepository.updateHomework(it.onEach { homework ->
homework.isNotified = true
})
}
}
private fun notify(homework: List<Homework>) {
notificationManager.notify(
Random.nextInt(Int.MAX_VALUE),
NotificationCompat.Builder(context, NewHomeworkChannel.CHANNEL_ID)
.setContentTitle(
context.resources.getQuantityString(
R.plurals.homework_notify_new_item_title,
homework.size,
homework.size
)
)
.setSmallIcon(R.drawable.ic_more_homework)
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent(
PendingIntent.getActivity(
context, MainView.Section.MESSAGE.id,
MainActivity.getStartIntent(context, MainView.Section.HOMEWORK, true),
PendingIntent.FLAG_UPDATE_CURRENT
)
)
.setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(
context.resources.getQuantityString(
R.plurals.homework_number_item,
homework.size,
homework.size
)
)
homework.forEach { addLine("${it.subject}: ${it.content}") }
this
})
.build()
)
}
}