mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-11-12 14:03:00 +01:00
Migrate to dao and add filter to title
This commit is contained in:
parent
b417cc51b2
commit
7f1f2247de
@ -1,12 +1,12 @@
|
||||
package com.pitchedapps.frost.db
|
||||
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL
|
||||
import com.pitchedapps.frost.services.NOTIF_CHANNEL_MESSAGES
|
||||
import com.pitchedapps.frost.services.NotificationContent
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class NotificationDbTest : BaseDbTest() {
|
||||
@ -38,6 +38,40 @@ class NotificationDbTest : BaseDbTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selectConditions() {
|
||||
runBlocking {
|
||||
val cookie1 = cookie(12345L)
|
||||
val cookie2 = cookie(12L)
|
||||
val notifs1 = (0L..2L).map { notifContent(it, cookie1) }
|
||||
val notifs2 = (5L..10L).map { notifContent(it, cookie2) }
|
||||
db.cookieDao().insertCookie(cookie1)
|
||||
db.cookieDao().insertCookie(cookie2)
|
||||
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1)
|
||||
dao.saveNotifications(NOTIF_CHANNEL_MESSAGES, notifs2)
|
||||
assertEquals(
|
||||
emptyList(),
|
||||
dao.selectNotifications(cookie1.id, NOTIF_CHANNEL_MESSAGES),
|
||||
"Filtering by type did not work for cookie1"
|
||||
)
|
||||
assertEquals(
|
||||
notifs1.sortedByDescending { it.timestamp },
|
||||
dao.selectNotifications(cookie1.id, NOTIF_CHANNEL_GENERAL),
|
||||
"Selection for cookie1 failed"
|
||||
)
|
||||
assertEquals(
|
||||
emptyList(),
|
||||
dao.selectNotifications(cookie2.id, NOTIF_CHANNEL_GENERAL),
|
||||
"Filtering by type did not work for cookie2"
|
||||
)
|
||||
assertEquals(
|
||||
notifs2.sortedByDescending { it.timestamp },
|
||||
dao.selectNotifications(cookie2.id, NOTIF_CHANNEL_MESSAGES),
|
||||
"Selection for cookie2 failed"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary key is both id and userId, in the event that the same notification to multiple users has the same id
|
||||
*/
|
||||
@ -50,8 +84,8 @@ class NotificationDbTest : BaseDbTest() {
|
||||
val notifs2 = notifs1.map { it.copy(data = cookie2) }
|
||||
db.cookieDao().insertCookie(cookie1)
|
||||
db.cookieDao().insertCookie(cookie2)
|
||||
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1)
|
||||
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs2)
|
||||
assertTrue(dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1), "Notif1 save failed")
|
||||
assertTrue(dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs2), "Notif2 save failed")
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,10 +118,11 @@ class NotificationDbTest : BaseDbTest() {
|
||||
|
||||
@Test
|
||||
fun insertionWithInvalidCookies() {
|
||||
assertFailsWith(SQLiteConstraintException::class) {
|
||||
runBlocking {
|
||||
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, listOf(notifContent(1L, cookie(2L))))
|
||||
}
|
||||
runBlocking {
|
||||
assertFalse(
|
||||
dao.saveNotifications(NOTIF_CHANNEL_GENERAL, listOf(notifContent(1L, cookie(2L)))),
|
||||
"Notif save should not have passed without relevant cookie entries"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -136,9 +136,19 @@ suspend fun NotificationDao.selectNotifications(userId: Long, type: String): Lis
|
||||
_selectNotifications(userId, type).map { it.toNotifContent() }
|
||||
}
|
||||
|
||||
suspend fun NotificationDao.saveNotifications(type: String, notifs: List<NotificationContent>) {
|
||||
withContext(Dispatchers.IO) {
|
||||
_saveNotifications(type, notifs)
|
||||
/**
|
||||
* Returns true if successful, given that there are constraints to the insertion
|
||||
*/
|
||||
suspend fun NotificationDao.saveNotifications(type: String, notifs: List<NotificationContent>): Boolean {
|
||||
if (notifs.isEmpty()) return true
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
_saveNotifications(type, notifs)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
L.e(e) { "Notif save failed" }
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,12 +185,4 @@ data class NotificationModel(
|
||||
|
||||
fun lastNotificationTime(id: Long): NotificationModel =
|
||||
(select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle()
|
||||
?: NotificationModel(id = id)
|
||||
|
||||
fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
|
||||
notificationModel.async save {
|
||||
L.d { "Fb notification model saved" }
|
||||
L._d { notificationModel }
|
||||
callback?.invoke()
|
||||
}
|
||||
}
|
||||
?: NotificationModel(id = id)
|
@ -32,9 +32,11 @@ import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.FrostWebActivity
|
||||
import com.pitchedapps.frost.db.CookieEntity
|
||||
import com.pitchedapps.frost.db.CookieModel
|
||||
import com.pitchedapps.frost.db.FrostDatabase
|
||||
import com.pitchedapps.frost.db.NotificationModel
|
||||
import com.pitchedapps.frost.db.lastNotificationTime
|
||||
import com.pitchedapps.frost.db.latestEpoch
|
||||
import com.pitchedapps.frost.db.saveNotifications
|
||||
import com.pitchedapps.frost.enums.OverlayContext
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.parsers.FrostParser
|
||||
@ -65,8 +67,8 @@ enum class NotificationType(
|
||||
private val overlayContext: OverlayContext,
|
||||
private val fbItem: FbItem,
|
||||
private val parser: FrostParser<ParseNotification>,
|
||||
// Legacy; remove with dbflow
|
||||
private val getTime: (notif: NotificationModel) -> Long,
|
||||
private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel,
|
||||
private val ringtone: () -> String
|
||||
) {
|
||||
|
||||
@ -76,7 +78,6 @@ enum class NotificationType(
|
||||
FbItem.NOTIFICATIONS,
|
||||
NotifParser,
|
||||
NotificationModel::epoch,
|
||||
{ notif, time -> notif.copy(epoch = time) },
|
||||
Prefs::notificationRingtone
|
||||
) {
|
||||
|
||||
@ -90,7 +91,6 @@ enum class NotificationType(
|
||||
FbItem.MESSAGES,
|
||||
MessageParser,
|
||||
NotificationModel::epochIm,
|
||||
{ notif, time -> notif.copy(epochIm = time) },
|
||||
Prefs::messageRingtone
|
||||
);
|
||||
|
||||
@ -117,7 +117,8 @@ enum class NotificationType(
|
||||
* Returns the number of notifications generated,
|
||||
* or -1 if an error occurred
|
||||
*/
|
||||
fun fetch(context: Context, data: CookieEntity): Int {
|
||||
suspend fun fetch(context: Context, data: CookieEntity): Int {
|
||||
val notifDao = FrostDatabase.get().notifDao()
|
||||
val response = try {
|
||||
parser.parse(data.cookie)
|
||||
} catch (ignored: Exception) {
|
||||
@ -128,35 +129,44 @@ enum class NotificationType(
|
||||
return -1
|
||||
}
|
||||
val notifContents = response.data.getUnreadNotifications(data).filter { notif ->
|
||||
val text = notif.text
|
||||
Prefs.notificationKeywords.none { text.contains(it, true) }
|
||||
val inText = notif.text.let { text ->
|
||||
Prefs.notificationKeywords.none { text.contains(it, true) }
|
||||
}
|
||||
val inTitle = notif.title?.let { title ->
|
||||
Prefs.notificationKeywords.none { title.contains(it, true) }
|
||||
} ?: false
|
||||
inText || inTitle
|
||||
}
|
||||
if (notifContents.isEmpty()) return 0
|
||||
val userId = data.id
|
||||
val prevNotifTime = lastNotificationTime(userId)
|
||||
val prevLatestEpoch = getTime(prevNotifTime)
|
||||
// Legacy, remove with dbflow
|
||||
val prevLatestEpoch =
|
||||
notifDao.latestEpoch(userId, channelId).takeIf { it != -1L } ?: getTime(lastNotificationTime(userId))
|
||||
L.v { "Notif $name prev epoch $prevLatestEpoch" }
|
||||
var newLatestEpoch = prevLatestEpoch
|
||||
val notifs = mutableListOf<FrostNotification>()
|
||||
notifContents.forEach { notif ->
|
||||
L.v { "Notif timestamp ${notif.timestamp}" }
|
||||
if (notif.timestamp <= prevLatestEpoch) return@forEach
|
||||
notifs.add(createNotification(context, notif))
|
||||
if (notif.timestamp > newLatestEpoch)
|
||||
newLatestEpoch = notif.timestamp
|
||||
}
|
||||
if (newLatestEpoch > prevLatestEpoch)
|
||||
putTime(prevNotifTime, newLatestEpoch).save()
|
||||
L.d { "Notif $name new epoch ${getTime(lastNotificationTime(userId))}" }
|
||||
if (prevLatestEpoch == -1L && !BuildConfig.DEBUG) {
|
||||
L.d { "Skipping first notification fetch" }
|
||||
return 0 // do not notify the first time
|
||||
}
|
||||
|
||||
val newNotifContents = notifContents.filter { it.timestamp > prevLatestEpoch }
|
||||
|
||||
if (newNotifContents.isEmpty()) {
|
||||
L.d { "No new notifs found for $name" }
|
||||
return 0
|
||||
}
|
||||
|
||||
L.d { "Notif $name new epoch ${newNotifContents.map { it.timestamp }.max()}" }
|
||||
|
||||
val notifs = newNotifContents.map { createNotification(context, it) }
|
||||
|
||||
notifDao.saveNotifications(channelId, newNotifContents)
|
||||
|
||||
frostEvent("Notifications", "Type" to name, "Count" to notifs.size)
|
||||
if (notifs.size > 1)
|
||||
summaryNotification(context, userId, notifs.size).notify(context)
|
||||
val ringtone = ringtone()
|
||||
notifs.forEachIndexed { i, notif ->
|
||||
// Ring at most twice
|
||||
notif.withAlert(i < 2, ringtone).notify(context)
|
||||
}
|
||||
return notifs.size
|
||||
|
@ -23,6 +23,7 @@ import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.db.CookieDao
|
||||
import com.pitchedapps.frost.db.CookieEntity
|
||||
import com.pitchedapps.frost.db.NotificationDao
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.frostEvent
|
||||
@ -44,6 +45,7 @@ import org.koin.android.ext.android.inject
|
||||
class NotificationService : BaseJobService() {
|
||||
|
||||
val cookieDao: CookieDao by inject()
|
||||
val notifDao: NotificationDao by inject()
|
||||
|
||||
override fun onStopJob(params: JobParameters?): Boolean {
|
||||
super.onStopJob(params)
|
||||
@ -110,7 +112,7 @@ class NotificationService : BaseJobService() {
|
||||
* Implemented fetch to also notify when an error occurs
|
||||
* Also normalized the output to return the number of notifications received
|
||||
*/
|
||||
private fun fetch(jobId: Int, type: NotificationType, cookie: CookieEntity): Int {
|
||||
private suspend fun fetch(jobId: Int, type: NotificationType, cookie: CookieEntity): Int {
|
||||
val count = type.fetch(this, cookie)
|
||||
if (count < 0) {
|
||||
if (jobId == NOTIFICATION_JOB_NOW)
|
||||
|
Loading…
Reference in New Issue
Block a user