This commit is contained in:
Philipp Heckel 2022-04-16 20:12:40 -04:00
parent 2d978fb4d6
commit 2f8be72c12
7 changed files with 78 additions and 4 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 9,
"identityHash": "5bab75c3b41c53c9855fe3a7ef8f0669",
"identityHash": "c1b4f54d1d3111dc5c8f02e8fa960ceb",
"entities": [
{
"tableName": "Subscription",
@ -82,7 +82,7 @@
},
{
"tableName": "Notification",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` 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, `deleted` INTEGER NOT NULL, `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, `title` TEXT NOT NULL, `message` 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, `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",
@ -145,6 +145,12 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "actions",
"columnName": "actions",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "deleted",
"columnName": "deleted",
@ -290,7 +296,7 @@
"views": [],
"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, '5bab75c3b41c53c9855fe3a7ef8f0669')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c1b4f54d1d3111dc5c8f02e8fa960ceb')"
]
}
}

View file

@ -133,6 +133,7 @@ class Backuper(val context: Context) {
priority = n.priority,
tags = n.tags,
click = n.click,
actions = null, // FIXME
attachment = attachment,
deleted = n.deleted
))

View file

@ -4,8 +4,10 @@ import android.content.Context
import androidx.room.*
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.heckel.ntfy.util.shortUrl
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.flow.Flow
import java.lang.reflect.Type
@Entity(indices = [Index(value = ["baseUrl", "topic"], unique = true), Index(value = ["upConnectorToken"], unique = true)])
data class Subscription(
@ -55,6 +57,7 @@ data class Notification(
@ColumnInfo(name = "priority", defaultValue = "3") val priority: Int, // 1=min, 3=default, 5=max
@ColumnInfo(name = "tags") val tags: String,
@ColumnInfo(name = "click") val click: String, // URL/intent to open on notification click
@ColumnInfo(name = "actions") val actions: List<Action>?,
@Embedded(prefix = "attachment_") val attachment: Attachment?,
@ColumnInfo(name = "deleted") val deleted: Boolean,
)
@ -73,6 +76,28 @@ data class Attachment(
this(name, type, size, expires, url, null, PROGRESS_NONE)
}
@Entity
data class Action(
@ColumnInfo(name = "action") val action: String,
@ColumnInfo(name = "label") val label: String,
@ColumnInfo(name = "url") val url: String?,
)
class Converters {
private val gson = Gson()
@TypeConverter
fun toActionList(value: String?): List<Action>? {
val listType: Type = object : TypeToken<List<Action>?>() {}.type
return gson.fromJson(value, listType)
}
@TypeConverter
fun fromActionList(list: List<Action>?): String {
return gson.toJson(list)
}
}
const val PROGRESS_NONE = -1
const val PROGRESS_INDETERMINATE = -2
const val PROGRESS_FAILED = -3
@ -102,6 +127,7 @@ data class LogEntry(
}
@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 9)
@TypeConverters(Converters::class)
abstract class Database : RoomDatabase() {
abstract fun subscriptionDao(): SubscriptionDao
abstract fun notificationDao(): NotificationDao

View file

@ -1,6 +1,7 @@
package io.heckel.ntfy.msg
import androidx.annotation.Keep
import io.heckel.ntfy.db.Action
/* This annotation ensures that proguard still works in production builds,
* see https://stackoverflow.com/a/62753300/1440785 */
@ -13,6 +14,7 @@ data class Message(
val priority: Int?,
val tags: List<String>?,
val click: String?,
val actions: List<MessageAction>?,
val title: String?,
val message: String,
val encoding: String?,
@ -28,4 +30,11 @@ data class MessageAttachment(
val url: String,
)
@Keep
data class MessageAction(
val action: String,
val label: String,
val url: String?,
)
const val MESSAGE_ENCODING_BASE64 = "base64"

View file

@ -2,6 +2,7 @@ package io.heckel.ntfy.msg
import android.util.Base64
import com.google.gson.Gson
import io.heckel.ntfy.db.Action
import io.heckel.ntfy.db.Attachment
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.util.joinTags
@ -29,6 +30,11 @@ class NotificationParser {
url = message.attachment.url,
)
} else null
val actions = if (message.actions != null) {
message.actions.map { action ->
Action(action.action, action.label, action.url)
}
} else null
val notification = Notification(
id = message.id,
subscriptionId = subscriptionId,
@ -39,6 +45,7 @@ class NotificationParser {
priority = toPriority(message.priority),
tags = joinTags(message.tags),
click = message.click ?: "",
actions = actions,
attachment = attachment,
notificationId = notificationId,
deleted = false

View file

@ -65,6 +65,7 @@ class NotificationService(val context: Context) {
maybeAddBrowseAction(builder, notification)
maybeAddDownloadAction(builder, notification)
maybeAddCancelAction(builder, notification)
maybeAddCustomActions(builder, notification)
maybeCreateNotificationChannel(notification.priority)
notificationManager.notify(notification.notificationId, builder.build())
@ -190,6 +191,29 @@ class NotificationService(val context: Context) {
}
}
private fun maybeAddCustomActions(builder: NotificationCompat.Builder, notification: Notification) {
notification.actions?.forEach { action ->
when (action.action) {
"view" -> maybeAddOpenUserAction(builder, notification, action)
}
}
}
private fun maybeAddOpenUserAction(builder: NotificationCompat.Builder, notification: Notification, action: Action) {
Log.d(TAG, "Adding user action $action")
val url = action.url ?: return
try {
val uri = Uri.parse(url)
val intent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
builder.addAction(NotificationCompat.Action.Builder(0, action.label, pendingIntent).build())
} catch (e: Exception) {
Log.w(TAG, "Unable to add open user action", e)
}
}
class DownloadBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val id = intent.getStringExtra("id") ?: return

View file

@ -131,6 +131,7 @@ class FirebaseService : FirebaseMessagingService() {
priority = toPriority(priority),
tags = tags ?: "",
click = click ?: "",
actions = null, // FIXME
attachment = attachment,
notificationId = Random.nextInt(),
deleted = false