aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt51
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt26
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt52
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt4
4 files changed, 91 insertions, 42 deletions
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt
index 2e9f1875..176d0d3a 100644
--- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt
@@ -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"
+ )
}
}
} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt
index 9622ec47..60ae2ae7 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt
@@ -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) \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
index 7da3c128..eb81ff04 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
@@ -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
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
index 088d8e0a..e1db5fa6 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -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)