Preview URL

This commit is contained in:
Philipp Heckel 2022-01-04 19:45:02 +01:00
parent f2bb3a022b
commit 9a5af648c4
5 changed files with 78 additions and 19 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 5, "version": 5,
"identityHash": "425a0bc96c8aae9d01985b0f4d7579dc", "identityHash": "fd7d1e0ac6ac7d68eb79ffe928dae67a",
"entities": [ "entities": [
{ {
"tableName": "Subscription", "tableName": "Subscription",
@ -80,7 +80,7 @@
}, },
{ {
"tableName": "Notification", "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, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `attachmentName` TEXT, `attachmentType` TEXT, `attachmentExpires` INTEGER, `attachmentUrl` TEXT, `deleted` INTEGER NOT NULL, 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, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `attachmentName` TEXT, `attachmentType` TEXT, `attachmentSize` INTEGER, `attachmentExpires` INTEGER, `attachmentPreviewUrl` TEXT, `attachmentUrl` TEXT, `deleted` INTEGER NOT NULL, PRIMARY KEY(`id`, `subscriptionId`))",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -143,12 +143,24 @@
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
}, },
{
"fieldPath": "attachmentSize",
"columnName": "attachmentSize",
"affinity": "INTEGER",
"notNull": false
},
{ {
"fieldPath": "attachmentExpires", "fieldPath": "attachmentExpires",
"columnName": "attachmentExpires", "columnName": "attachmentExpires",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": false "notNull": false
}, },
{
"fieldPath": "attachmentPreviewUrl",
"columnName": "attachmentPreviewUrl",
"affinity": "TEXT",
"notNull": false
},
{ {
"fieldPath": "attachmentUrl", "fieldPath": "attachmentUrl",
"columnName": "attachmentUrl", "columnName": "attachmentUrl",
@ -176,7 +188,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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, '425a0bc96c8aae9d01985b0f4d7579dc')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fd7d1e0ac6ac7d68eb79ffe928dae67a')"
] ]
} }
} }

View file

@ -55,6 +55,7 @@ data class Notification(
@ColumnInfo(name = "attachmentType") val attachmentType: String?, // MIME type @ColumnInfo(name = "attachmentType") val attachmentType: String?, // MIME type
@ColumnInfo(name = "attachmentSize") val attachmentSize: Long?, // Size in bytes @ColumnInfo(name = "attachmentSize") val attachmentSize: Long?, // Size in bytes
@ColumnInfo(name = "attachmentExpires") val attachmentExpires: Long?, // Unix timestamp @ColumnInfo(name = "attachmentExpires") val attachmentExpires: Long?, // Unix timestamp
@ColumnInfo(name = "attachmentPreviewUrl") val attachmentPreviewUrl: String?,
@ColumnInfo(name = "attachmentUrl") val attachmentUrl: String?, @ColumnInfo(name = "attachmentUrl") val attachmentUrl: String?,
@ColumnInfo(name = "deleted") val deleted: Boolean, @ColumnInfo(name = "deleted") val deleted: Boolean,
) )

View file

@ -123,7 +123,8 @@ class ApiService {
attachmentName = message.attachment?.name, attachmentName = message.attachment?.name,
attachmentType = message.attachment?.type, attachmentType = message.attachment?.type,
attachmentSize = message.attachment?.size, attachmentSize = message.attachment?.size,
attachmentExpires = message.attachment?.expires?.toLong(), attachmentExpires = message.attachment?.expires,
attachmentPreviewUrl = message.attachment?.preview_url,
attachmentUrl = message.attachment?.url, attachmentUrl = message.attachment?.url,
notificationId = Random.nextInt(), notificationId = Random.nextInt(),
deleted = false deleted = false
@ -158,6 +159,7 @@ class ApiService {
attachmentType = message.attachment?.type, attachmentType = message.attachment?.type,
attachmentSize = message.attachment?.size, attachmentSize = message.attachment?.size,
attachmentExpires = message.attachment?.expires, attachmentExpires = message.attachment?.expires,
attachmentPreviewUrl = message.attachment?.preview_url,
attachmentUrl = message.attachment?.url, attachmentUrl = message.attachment?.url,
notificationId = 0, notificationId = 0,
deleted = false deleted = false
@ -182,9 +184,10 @@ class ApiService {
@Keep @Keep
private data class Attachment( private data class Attachment(
val name: String, val name: String,
val type: String, val type: String?,
val size: Long, val size: Long?,
val expires: Long, val expires: Long?,
val preview_url: String?,
val url: String, val url: String,
) )

View file

@ -9,7 +9,9 @@ import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.RingtoneManager import android.media.RingtoneManager
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -22,8 +24,10 @@ import io.heckel.ntfy.util.formatMessage
import io.heckel.ntfy.util.formatTitle import io.heckel.ntfy.util.formatTitle
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class NotificationService(val context: Context) { class NotificationService(val context: Context) {
private val client = OkHttpClient.Builder() private val client = OkHttpClient.Builder()
.callTimeout(15, TimeUnit.SECONDS) // Total timeout for entire request .callTimeout(15, TimeUnit.SECONDS) // Total timeout for entire request
@ -35,11 +39,9 @@ class NotificationService(val context: Context) {
fun display(subscription: Subscription, notification: Notification) { fun display(subscription: Subscription, notification: Notification) {
Log.d(TAG, "Displaying notification $notification") Log.d(TAG, "Displaying notification $notification")
val imageAttachment = notification.attachmentUrl != null && (notification.attachmentType?.startsWith("image/") ?: false)
if (imageAttachment) {
downloadImageAndDisplay(subscription, notification)
} else {
displayInternal(subscription, notification) displayInternal(subscription, notification)
if (notification.attachmentPreviewUrl != null) {
downloadPreviewAndUpdate(subscription, notification)
} }
} }
@ -81,8 +83,19 @@ class NotificationService(val context: Context) {
.setSound(defaultSoundUri) .setSound(defaultSoundUri)
.setContentIntent(pendingIntent) // Click target for notification .setContentIntent(pendingIntent) // Click target for notification
.setAutoCancel(true) // Cancel when notification is clicked .setAutoCancel(true) // Cancel when notification is clicked
if (notification.attachmentUrl != null) {
val viewIntent = PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW, Uri.parse(notification.attachmentUrl)), 0)
notificationBuilder
.addAction(NotificationCompat.Action.Builder(0, "Open", viewIntent).build())
.addAction(NotificationCompat.Action.Builder(0, "Copy URL", viewIntent).build())
.addAction(NotificationCompat.Action.Builder(0, "Download", pendingIntent).build())
}
notificationBuilder = if (bitmap != null) { notificationBuilder = if (bitmap != null) {
notificationBuilder.setStyle(NotificationCompat.BigPictureStyle().bigPicture(bitmap)) notificationBuilder
.setLargeIcon(bitmap)
.setStyle(NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.bigLargeIcon(null))
} else { } else {
notificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(message)) notificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(message))
} }
@ -92,9 +105,29 @@ class NotificationService(val context: Context) {
notificationManager.notify(notification.notificationId, notificationBuilder.build()) notificationManager.notify(notification.notificationId, notificationBuilder.build())
} }
private fun downloadImageAndDisplay(subscription: Subscription, notification: Notification) { private fun downloadPreviewAndUpdate(subscription: Subscription, notification: Notification) {
val previewUrl = notification.attachmentPreviewUrl ?: return
Log.d(TAG, "Downloading preview image $previewUrl")
val request = Request.Builder()
.url(previewUrl)
.addHeader("User-Agent", ApiService.USER_AGENT)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful || response.body == null) {
Log.d(TAG, "Preview response failed: ${response.code}")
} else {
Log.d(TAG, "Successful response, streaming preview")
val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream())
displayInternal(subscription, notification, bitmap)
}
}
}
private fun downloadPreviewAndUpdateXXX(subscription: Subscription, notification: Notification) {
val url = notification.attachmentUrl ?: return val url = notification.attachmentUrl ?: return
Log.d(TAG, "Downloading image $url") Log.d(TAG, "Downloading attachment from $url")
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
@ -102,13 +135,21 @@ class NotificationService(val context: Context) {
.build() .build()
client.newCall(request).execute().use { response -> client.newCall(request).execute().use { response ->
if (!response.isSuccessful || response.body == null) { if (!response.isSuccessful || response.body == null) {
displayInternal(subscription, notification) Log.d(TAG, "Attachment download failed: ${response.code}")
return } else {
} Log.d(TAG, "Successful response")
/*val filename = notification.id
val dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS + "/ntfy/" + notification.id)
context.openFileOutput(filename, Context.MODE_PRIVATE).use {
response.body!!.byteStream()
}*/
// TODO work manager
val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream()) val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream())
displayInternal(subscription, notification, bitmap) displayInternal(subscription, notification, bitmap)
} }
} }
}
private fun maybeCreateNotificationChannel(notificationManager: NotificationManager, priority: Int) { private fun maybeCreateNotificationChannel(notificationManager: NotificationManager, priority: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View file

@ -60,6 +60,7 @@ class FirebaseService : FirebaseMessagingService() {
val attachmentType = data["attachment_type"] val attachmentType = data["attachment_type"]
val attachmentSize = data["attachment_size"]?.toLongOrNull() val attachmentSize = data["attachment_size"]?.toLongOrNull()
val attachmentExpires = data["attachment_expires"]?.toLongOrNull() val attachmentExpires = data["attachment_expires"]?.toLongOrNull()
val attachmentPreviewUrl = data["attachment_preview_url"]
val attachmentUrl = data["attachment_url"] val attachmentUrl = data["attachment_url"]
if (id == null || topic == null || message == null || timestamp == null) { if (id == null || topic == null || message == null || timestamp == null) {
Log.d(TAG, "Discarding unexpected message: from=${remoteMessage.from}, data=${data}") Log.d(TAG, "Discarding unexpected message: from=${remoteMessage.from}, data=${data}")
@ -84,6 +85,7 @@ class FirebaseService : FirebaseMessagingService() {
attachmentType = attachmentType, attachmentType = attachmentType,
attachmentSize = attachmentSize, attachmentSize = attachmentSize,
attachmentExpires = attachmentExpires, attachmentExpires = attachmentExpires,
attachmentPreviewUrl = attachmentPreviewUrl,
attachmentUrl = attachmentUrl, attachmentUrl = attachmentUrl,
notificationId = Random.nextInt(), notificationId = Random.nextInt(),
deleted = false deleted = false