This commit is contained in:
Philipp Heckel 2026-01-13 16:30:57 -05:00
parent a69614df35
commit b96c2fb2ed
9 changed files with 21 additions and 22 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 18,
"identityHash": "e62fdd1a12610e3514eff4dc83dcc0b8",
"identityHash": "02663facc6503d5ea7015397d5e8cc94",
"entities": [
{
"tableName": "Subscription",
@ -118,7 +118,7 @@
},
{
"tableName": "Notification",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `sequence_id` TEXT NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `contentType` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `icon_url` TEXT, `icon_contentUri` TEXT, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `sequenceId` TEXT NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `contentType` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `icon_url` TEXT, `icon_contentUri` TEXT, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))",
"fields": [
{
"fieldPath": "id",
@ -140,7 +140,7 @@
},
{
"fieldPath": "sequenceId",
"columnName": "sequence_id",
"columnName": "sequenceId",
"affinity": "TEXT",
"notNull": true
},
@ -423,7 +423,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e62fdd1a12610e3514eff4dc83dcc0b8')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02663facc6503d5ea7015397d5e8cc94')"
]
}
}

View file

@ -146,7 +146,7 @@ data class Notification(
@ColumnInfo(name = "id") val id: String,
@ColumnInfo(name = "subscriptionId") val subscriptionId: Long,
@ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp in seconds
@ColumnInfo(name = "sequence_id") val sequenceId: String, // Sequence ID for updating notifications
@ColumnInfo(name = "sequenceId") val sequenceId: String, // Sequence ID for updating notifications
@ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "message") val message: String,
@ColumnInfo(name = "contentType") val contentType: String, // "" or "text/markdown" (empty assume text/plain)
@ -159,7 +159,7 @@ data class Notification(
@ColumnInfo(name = "actions") val actions: List<Action>?,
@Embedded(prefix = "attachment_") val attachment: Attachment?,
@ColumnInfo(name = "deleted") val deleted: Boolean,
@Ignore val event: String = ApiService.EVENT_MESSAGE, // In-memory event type (message, message_delete, message_read)
@Ignore val event: String = ApiService.EVENT_MESSAGE, // In-memory event type (message, message_delete, message_clear)
) {
constructor(
id: String,
@ -479,8 +479,8 @@ abstract class Database : RoomDatabase() {
private val MIGRATION_17_18 = object : Migration(17, 18) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE Notification ADD COLUMN sequence_id TEXT NOT NULL DEFAULT ''")
db.execSQL("UPDATE Notification SET sequence_id = id WHERE sequence_id = ''")
db.execSQL("ALTER TABLE Notification ADD COLUMN sequenceId TEXT NOT NULL DEFAULT ''")
db.execSQL("UPDATE Notification SET sequenceId = id WHERE sequenceId = ''")
}
}
}
@ -595,13 +595,13 @@ interface NotificationDao {
@Query("UPDATE notification SET notificationId = 0 WHERE subscriptionId = :subscriptionId")
fun markAllAsRead(subscriptionId: Long)
@Query("UPDATE notification SET notificationId = 0 WHERE subscriptionId = :subscriptionId AND sequence_id = :sequenceId")
@Query("UPDATE notification SET notificationId = 0 WHERE subscriptionId = :subscriptionId AND sequenceId = :sequenceId")
fun markAsReadBySequenceId(subscriptionId: Long, sequenceId: String)
@Query("UPDATE notification SET deleted = 1 WHERE id = :notificationId")
fun markAsDeleted(notificationId: String)
@Query("UPDATE notification SET deleted = 1 WHERE subscriptionId = :subscriptionId AND sequence_id = :sequenceId")
@Query("UPDATE notification SET deleted = 1 WHERE subscriptionId = :subscriptionId AND sequenceId = :sequenceId")
fun markAsDeletedBySequenceId(subscriptionId: Long, sequenceId: String)
@Query("UPDATE notification SET deleted = 1 WHERE subscriptionId = :subscriptionId")

View file

@ -198,7 +198,7 @@ class ApiService(private val context: Context) {
const val CONTROL_TOPIC = "~control"
const val EVENT_MESSAGE = "message"
const val EVENT_MESSAGE_DELETE = "message_delete"
const val EVENT_MESSAGE_READ = "message_read"
const val EVENT_MESSAGE_CLEAR = "message_clear"
const val EVENT_KEEPALIVE = "keepalive"
const val EVENT_POLL_REQUEST = "poll_request"
}

View file

@ -10,7 +10,6 @@ data class Message(
val id: String,
val time: Long,
@SerializedName("sequence_id") val sequenceId: String?, // Sequence ID for updating notifications
val deleted: Boolean?, // true if the notification sequence is deleted
val event: String,
val topic: String,
val priority: Int?,
@ -19,7 +18,7 @@ data class Message(
val icon: String?,
val actions: List<MessageAction>?,
val title: String?,
val message: String,
val message: String?,
@SerializedName("content_type") val contentType: String?,
val encoding: String?,
val attachment: MessageAttachment?,

View file

@ -79,7 +79,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
}
private fun shouldCancel(notification: Notification): Boolean {
return notification.event == ApiService.EVENT_MESSAGE_READ || notification.event == ApiService.EVENT_MESSAGE_DELETE
return notification.event == ApiService.EVENT_MESSAGE_CLEAR || notification.event == ApiService.EVENT_MESSAGE_DELETE
}
private fun shouldNotify(subscription: Subscription, notification: Notification, muted: Boolean): Boolean {

View file

@ -23,7 +23,7 @@ class NotificationParser {
val message = gson.fromJson(s, Message::class.java)
val validEvent = message.event == ApiService.EVENT_MESSAGE ||
message.event == ApiService.EVENT_MESSAGE_DELETE ||
message.event == ApiService.EVENT_MESSAGE_READ
message.event == ApiService.EVENT_MESSAGE_CLEAR
if (!validEvent) {
return null
}
@ -60,7 +60,7 @@ class NotificationParser {
timestamp = message.time,
sequenceId = sequenceId,
title = message.title ?: "",
message = message.message,
message = message.message ?: "",
contentType = message.contentType ?: "",
encoding = message.encoding ?: "",
priority = toPriority(message.priority),

View file

@ -60,7 +60,7 @@ class Poller(
// Handle delete and read events
latestBySequenceId
.filter { it.event == ApiService.EVENT_MESSAGE_READ || it.event == ApiService.EVENT_MESSAGE_DELETE }
.filter { it.event == ApiService.EVENT_MESSAGE_CLEAR || it.event == ApiService.EVENT_MESSAGE_DELETE }
.forEach { notification ->
val sequenceId = notification.sequenceId.ifEmpty { notification.id }
when (notification.event) {
@ -68,7 +68,7 @@ class Poller(
Log.d(TAG, "Deleting notifications with sequenceId $sequenceId")
repository.markAsDeletedBySequenceId(subscriptionId, sequenceId)
}
ApiService.EVENT_MESSAGE_READ -> {
ApiService.EVENT_MESSAGE_CLEAR -> {
Log.d(TAG, "Marking notifications as read with sequenceId $sequenceId")
repository.markAsReadBySequenceId(subscriptionId, sequenceId)
}

View file

@ -329,7 +329,7 @@ class SubscriberService : Service() {
// and the web app hooks.js:handleNotification().
when (notification.event) {
ApiService.EVENT_MESSAGE_READ -> {
ApiService.EVENT_MESSAGE_CLEAR -> {
if (notification.sequenceId.isNotEmpty()) {
repository.markAsReadBySequenceId(subscription.id, notification.sequenceId)
}

View file

@ -51,7 +51,7 @@ class FirebaseService : FirebaseMessagingService() {
when (data["event"]) {
ApiService.EVENT_MESSAGE -> handleMessage(remoteMessage)
ApiService.EVENT_MESSAGE_DELETE -> handleMessageDelete(remoteMessage)
ApiService.EVENT_MESSAGE_READ -> handleMessageRead(remoteMessage)
ApiService.EVENT_MESSAGE_CLEAR -> handleMessageClear(remoteMessage)
ApiService.EVENT_KEEPALIVE -> handleKeepalive(remoteMessage)
ApiService.EVENT_POLL_REQUEST -> handlePollRequest(remoteMessage)
else -> Log.d(TAG, "Discarding unexpected message (2): from=${remoteMessage.from}, data=${data}")
@ -108,11 +108,11 @@ class FirebaseService : FirebaseMessagingService() {
}
}
private fun handleMessageRead(remoteMessage: RemoteMessage) {
private fun handleMessageClear(remoteMessage: RemoteMessage) {
val data = remoteMessage.data
val topic = data["topic"] ?: return
val sequenceId = data["sequence_id"] ?: return
Log.d(TAG, "Received message_read: from=${remoteMessage.from}, topic=$topic, sequenceId=$sequenceId")
Log.d(TAG, "Received message_clear: from=${remoteMessage.from}, topic=$topic, sequenceId=$sequenceId")
CoroutineScope(job).launch {
val baseUrl = getString(R.string.app_base_url)