Android notification ID
This commit is contained in:
parent
a23d3991a1
commit
41f46b667f
7 changed files with 28 additions and 15 deletions
|
|
@ -16,7 +16,6 @@ import java.io.IOException
|
|||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets.UTF_8
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.random.Random
|
||||
|
||||
class ApiService(context: Context) {
|
||||
private val repository = Repository.getInstance(context)
|
||||
|
|
@ -139,7 +138,7 @@ class ApiService(context: Context) {
|
|||
val body = response.body.string().trim()
|
||||
if (body.isEmpty()) return emptyList()
|
||||
val notifications = body.lines().mapNotNull { line ->
|
||||
parser.parse(line, subscriptionId = subscriptionId, notificationId = 0) // No notification when we poll
|
||||
parser.parse(line, subscriptionId = subscriptionId) // No notification when we poll
|
||||
}
|
||||
|
||||
Log.d(TAG, "Notifications: $notifications")
|
||||
|
|
@ -170,7 +169,7 @@ class ApiService(context: Context) {
|
|||
val source = response.body.source()
|
||||
while (!source.exhausted()) {
|
||||
val line = source.readUtf8Line() ?: throw Exception("Unexpected response for $url: line is null")
|
||||
val notification = parser.parseWithTopic(line, notificationId = Random.nextInt(), subscriptionId = 0) // subscriptionId to be set downstream
|
||||
val notification = parser.parseWithTopic(line, notify = true, subscriptionId = 0) // subscriptionId to be set downstream
|
||||
if (notification != null) {
|
||||
notify(notification.topic, notification.notification)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import io.heckel.ntfy.db.Action
|
|||
import io.heckel.ntfy.db.Attachment
|
||||
import io.heckel.ntfy.db.Icon
|
||||
import io.heckel.ntfy.db.Notification
|
||||
import io.heckel.ntfy.util.deriveNotificationId
|
||||
import io.heckel.ntfy.util.joinTags
|
||||
import io.heckel.ntfy.util.toPriority
|
||||
import java.lang.reflect.Type
|
||||
|
|
@ -13,12 +14,12 @@ import java.lang.reflect.Type
|
|||
class NotificationParser {
|
||||
private val gson = Gson()
|
||||
|
||||
fun parse(s: String, subscriptionId: Long = 0, notificationId: Int = 0): Notification? {
|
||||
val notificationWithTopic = parseWithTopic(s, subscriptionId = subscriptionId, notificationId = notificationId)
|
||||
fun parse(s: String, subscriptionId: Long = 0, notify: Boolean = false): Notification? {
|
||||
val notificationWithTopic = parseWithTopic(s, subscriptionId = subscriptionId, notify = notify)
|
||||
return notificationWithTopic?.notification
|
||||
}
|
||||
|
||||
fun parseWithTopic(s: String, subscriptionId: Long = 0, notificationId: Int = 0): NotificationWithTopic? {
|
||||
fun parseWithTopic(s: String, subscriptionId: Long = 0, notify: Boolean = false): NotificationWithTopic? {
|
||||
val message = gson.fromJson(s, Message::class.java)
|
||||
if (message.event != ApiService.EVENT_MESSAGE) {
|
||||
return null
|
||||
|
|
@ -50,6 +51,8 @@ class NotificationParser {
|
|||
}
|
||||
val icon: Icon? = if (message.icon != null && message.icon != "") Icon(url = message.icon) else null
|
||||
val sid = message.sid ?: message.id // Default to id if sid not provided
|
||||
// Derive notificationId from sid so updates replace the existing Android notification
|
||||
val notificationId = if (notify) deriveNotificationId(sid) else 0
|
||||
val notification = Notification(
|
||||
id = message.id,
|
||||
subscriptionId = subscriptionId,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import java.util.*
|
|||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* Connect to ntfy server via WebSockets. This connection represents a single connection to a server, with
|
||||
|
|
@ -148,7 +147,7 @@ class WsConnection(
|
|||
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||
synchronize("onMessage") {
|
||||
Log.d(TAG, "$shortUrl (gid=$globalId, lid=$id): Received message: $text")
|
||||
val notificationWithTopic = parser.parseWithTopic(text, subscriptionId = 0, notificationId = Random.nextInt())
|
||||
val notificationWithTopic = parser.parseWithTopic(text, subscriptionId = 0, notify = true)
|
||||
if (notificationWithTopic == null) {
|
||||
Log.d(TAG, "$shortUrl (gid=$globalId, lid=$id): Irrelevant or unknown message. Discarding.")
|
||||
return@synchronize
|
||||
|
|
|
|||
|
|
@ -67,13 +67,13 @@ import io.heckel.ntfy.util.randomSubscriptionId
|
|||
import io.heckel.ntfy.util.shortUrl
|
||||
import io.heckel.ntfy.util.topicShortUrl
|
||||
import io.heckel.ntfy.work.DeleteWorker
|
||||
import io.heckel.ntfy.util.deriveNotificationId
|
||||
import io.heckel.ntfy.work.PollWorker
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.random.Random
|
||||
import androidx.core.view.size
|
||||
import androidx.core.view.get
|
||||
import androidx.core.net.toUri
|
||||
|
|
@ -709,7 +709,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific
|
|||
val newNotifications = repository.onlyNewNotifications(subscription.id, notifications)
|
||||
newNotifications.forEach { notification ->
|
||||
newNotificationsCount++
|
||||
val notificationWithId = notification.copy(notificationId = Random.nextInt())
|
||||
val notificationWithId = notification.copy(notificationId = deriveNotificationId(notification.sid))
|
||||
if (repository.addNotification(notificationWithId)) {
|
||||
dispatcher?.dispatch(subscription, notificationWithId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -503,3 +503,14 @@ fun Button.dangerButton() {
|
|||
fun Long.nullIfZero(): Long? {
|
||||
return if (this == 0L) return null else this
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a stable notification ID from a string (typically the sid or id).
|
||||
* This allows Android to update existing notifications when a new version arrives.
|
||||
* The result is always positive and never zero (0 means "no notification").
|
||||
*/
|
||||
fun deriveNotificationId(sid: String): Int {
|
||||
val hash = sid.hashCode()
|
||||
// Ensure the ID is positive and non-zero
|
||||
return if (hash == 0 || hash == Int.MIN_VALUE) 1 else kotlin.math.abs(hash)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import io.heckel.ntfy.db.Repository
|
|||
import io.heckel.ntfy.msg.ApiService
|
||||
import io.heckel.ntfy.msg.NotificationDispatcher
|
||||
import io.heckel.ntfy.util.Log
|
||||
import io.heckel.ntfy.util.deriveNotificationId
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.random.Random
|
||||
|
||||
class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
|
||||
// IMPORTANT:
|
||||
|
|
@ -49,7 +49,7 @@ class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
|
|||
)
|
||||
val newNotifications = repository
|
||||
.onlyNewNotifications(subscription.id, notifications)
|
||||
.map { it.copy(notificationId = Random.nextInt()) }
|
||||
.map { it.copy(notificationId = deriveNotificationId(it.sid)) }
|
||||
newNotifications.forEach { notification ->
|
||||
if (repository.addNotification(notification)) {
|
||||
dispatcher.dispatch(subscription, notification)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import io.heckel.ntfy.msg.ApiService
|
|||
import io.heckel.ntfy.msg.NotificationDispatcher
|
||||
import io.heckel.ntfy.msg.NotificationParser
|
||||
import io.heckel.ntfy.service.SubscriberService
|
||||
import io.heckel.ntfy.util.deriveNotificationId
|
||||
import io.heckel.ntfy.util.nullIfZero
|
||||
import io.heckel.ntfy.util.toPriority
|
||||
import io.heckel.ntfy.util.topicShortUrl
|
||||
|
|
@ -21,7 +22,6 @@ import io.heckel.ntfy.work.PollWorker
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.random.Random
|
||||
|
||||
class FirebaseService : FirebaseMessagingService() {
|
||||
private val repository by lazy { (application as Application).repository }
|
||||
|
|
@ -128,11 +128,12 @@ class FirebaseService : FirebaseMessagingService() {
|
|||
)
|
||||
} else null
|
||||
val icon: Icon? = if (iconUrl != null && iconUrl != "") Icon(url = iconUrl) else null
|
||||
val actualSid = sid ?: id
|
||||
val notification = Notification(
|
||||
id = id,
|
||||
subscriptionId = subscription.id,
|
||||
timestamp = timestamp,
|
||||
sid = sid ?: id,
|
||||
sid = actualSid,
|
||||
title = title ?: "",
|
||||
message = message,
|
||||
contentType = contentType ?: "",
|
||||
|
|
@ -143,7 +144,7 @@ class FirebaseService : FirebaseMessagingService() {
|
|||
icon = icon,
|
||||
actions = parser.parseActions(actions),
|
||||
attachment = attachment,
|
||||
notificationId = Random.nextInt(),
|
||||
notificationId = deriveNotificationId(actualSid),
|
||||
deleted = false
|
||||
)
|
||||
if (repository.addNotification(notification)) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue