Actions WIP

This commit is contained in:
Philipp Heckel 2022-04-19 09:15:06 -04:00
parent 686616d4d2
commit 79c0e91e8d
11 changed files with 153 additions and 74 deletions

View file

@ -73,20 +73,33 @@ data class Attachment(
@ColumnInfo(name = "progress") val progress: Int, // Progress during download, -1 if not downloaded
) {
constructor(name: String, type: String?, size: Long?, expires: Long?, url: String) :
this(name, type, size, expires, url, null, PROGRESS_NONE)
this(name, type, size, expires, url, null, ATTACHMENT_PROGRESS_NONE)
}
const val ATTACHMENT_PROGRESS_NONE = -1
const val ATTACHMENT_PROGRESS_INDETERMINATE = -2
const val ATTACHMENT_PROGRESS_FAILED = -3
const val ATTACHMENT_PROGRESS_DELETED = -4
const val ATTACHMENT_PROGRESS_DONE = 100
@Entity
data class Action(
@ColumnInfo(name = "id") val id: String, // Synthetic ID to identify result, and easily pass via Broadcast and WorkManager
@ColumnInfo(name = "action") val action: String,
@ColumnInfo(name = "action") val action: String, // "view", "http" or "broadcast"
@ColumnInfo(name = "label") val label: String,
@ColumnInfo(name = "url") val url: String?, // used in "view" and "http"
@ColumnInfo(name = "method") val method: String?, // used in "http"
@ColumnInfo(name = "headers") val headers: Map<String,String>?, // used in "http"
@ColumnInfo(name = "body") val body: String?, // used in "http"
@ColumnInfo(name = "url") val url: String?, // used in "view" and "http" actions
@ColumnInfo(name = "method") val method: String?, // used in "http" action
@ColumnInfo(name = "headers") val headers: Map<String,String>?, // used in "http" action
@ColumnInfo(name = "body") val body: String?, // used in "http" action
@ColumnInfo(name = "extras") val extras: Map<String,String>?, // used in "broadcast" action
@ColumnInfo(name = "progress") val progress: Int?, // used to indicate progress in popup
@ColumnInfo(name = "error") val error: String?, // used to indicate errors in popup
)
const val ACTION_PROGRESS_ONGOING = 1
const val ACTION_PROGRESS_SUCCESS = 2
const val ACTION_PROGRESS_FAILED = 3
class Converters {
private val gson = Gson()
@ -102,12 +115,6 @@ class Converters {
}
}
const val PROGRESS_NONE = -1
const val PROGRESS_INDETERMINATE = -2
const val PROGRESS_FAILED = -3
const val PROGRESS_DELETED = -4
const val PROGRESS_DONE = 100
@Entity
data class User(
@PrimaryKey @ColumnInfo(name = "baseUrl") val baseUrl: String,

View file

@ -2,8 +2,8 @@ package io.heckel.ntfy.msg
import android.content.Context
import android.content.Intent
import android.util.Base64
import io.heckel.ntfy.R
import io.heckel.ntfy.db.Action
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
@ -17,7 +17,7 @@ import kotlinx.coroutines.launch
* in order to facilitate tasks app integrations.
*/
class BroadcastService(private val ctx: Context) {
fun send(subscription: Subscription, notification: Notification, muted: Boolean) {
fun sendMessage(subscription: Subscription, notification: Notification, muted: Boolean) {
val intent = Intent()
intent.action = MESSAGE_RECEIVED_ACTION
intent.putExtra("id", notification.id)
@ -34,7 +34,17 @@ class BroadcastService(private val ctx: Context) {
intent.putExtra("muted", muted)
intent.putExtra("muted_str", muted.toString())
Log.d(TAG, "Sending intent broadcast: $intent")
Log.d(TAG, "Sending message intent broadcast: $intent")
ctx.sendBroadcast(intent)
}
fun sendUserAction(action: Action) {
val intent = Intent()
intent.action = USER_ACTION_ACTION
action.extras?.forEach { (key, value) ->
intent.putExtra(key, value)
}
Log.d(TAG, "Sending user action intent broadcast: $intent")
ctx.sendBroadcast(intent)
}
@ -109,5 +119,6 @@ class BroadcastService(private val ctx: Context) {
// These constants cannot be changed without breaking the contract; also see manifest
private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED"
private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE"
private const val USER_ACTION_ACTION = "io.heckel.ntfy.USER_ACTION"
}
}

View file

@ -91,13 +91,13 @@ class DownloadWorker(private val context: Context, params: WorkerParameters) : W
while (bytes >= 0) {
if (System.currentTimeMillis() - lastProgress > NOTIFICATION_UPDATE_INTERVAL_MILLIS) {
if (isStopped) { // Canceled by user
save(attachment.copy(progress = PROGRESS_NONE))
save(attachment.copy(progress = ATTACHMENT_PROGRESS_NONE))
return // File will be deleted in onStopped()
}
val progress = if (attachment.size != null && attachment.size!! > 0) {
(bytesCopied.toFloat()/attachment.size!!.toFloat()*100).toInt()
} else {
PROGRESS_INDETERMINATE
ATTACHMENT_PROGRESS_INDETERMINATE
}
save(attachment.copy(progress = progress))
lastProgress = System.currentTimeMillis()
@ -114,7 +114,7 @@ class DownloadWorker(private val context: Context, params: WorkerParameters) : W
save(attachment.copy(
size = bytesCopied,
contentUri = uri.toString(),
progress = PROGRESS_DONE
progress = ATTACHMENT_PROGRESS_DONE
))
}
} catch (e: Exception) {
@ -155,7 +155,7 @@ class DownloadWorker(private val context: Context, params: WorkerParameters) : W
private fun failed(e: Exception) {
Log.w(TAG, "Attachment download failed", e)
save(attachment.copy(progress = PROGRESS_FAILED))
save(attachment.copy(progress = ATTACHMENT_PROGRESS_FAILED))
maybeDeleteFile()
}

View file

@ -34,11 +34,12 @@ data class MessageAttachment(
data class MessageAction(
val id: String,
val action: String,
val label: String,
val label: String, // "view", "broadcast" or "http"
val url: String?, // used in "view" and "http"
val method: String?, // used in "http"
val method: String?, // used in "http", default is POST (!)
val headers: Map<String,String>?, // used in "http"
val body: String?, // used in "http"
val extras: Map<String,String>?, // used in "broadcast"
)
const val MESSAGE_ENCODING_BASE64 = "base64"

View file

@ -1,7 +1,6 @@
package io.heckel.ntfy.msg
import android.content.Context
import android.util.Base64
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
@ -35,7 +34,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
notifier.display(subscription, notification)
}
if (broadcast) {
broadcaster.send(subscription, notification, muted)
broadcaster.sendMessage(subscription, notification, muted)
}
if (distribute) {
safeLet(subscription.upAppId, subscription.upConnectorToken) { appId, connectorToken ->

View file

@ -33,7 +33,7 @@ class NotificationParser {
} else null
val actions = if (message.actions != null) {
message.actions.map { a ->
Action(a.id, a.action, a.label, a.url, a.method, a.headers, a.body)
Action(a.id, a.action, a.label, a.url, a.method, a.headers, a.body, a.extras, null, null)
}
} else null
val notification = Notification(

View file

@ -66,7 +66,7 @@ class NotificationService(val context: Context) {
maybeAddBrowseAction(builder, notification)
maybeAddDownloadAction(builder, notification)
maybeAddCancelAction(builder, notification)
maybeAddCustomActions(builder, notification)
maybeAddUserActions(builder, notification)
maybeCreateNotificationChannel(notification.priority)
notificationManager.notify(notification.notificationId, builder.build())
@ -90,43 +90,55 @@ class NotificationService(val context: Context) {
val bitmapStream = resolver.openInputStream(Uri.parse(contentUri))
val bitmap = BitmapFactory.decodeStream(bitmapStream)
builder
.setContentText(formatMessage(notification))
.setContentText(maybeAppendActionErrors(formatMessage(notification), notification))
.setLargeIcon(bitmap)
.setStyle(NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.bigLargeIcon(null))
} catch (_: Exception) {
val message = formatMessageMaybeWithAttachmentInfo(notification)
val message = maybeAppendActionErrors(formatMessageMaybeWithAttachmentInfos(notification), notification)
builder
.setContentText(message)
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
}
} else {
val message = formatMessageMaybeWithAttachmentInfo(notification)
val message = maybeAppendActionErrors(formatMessageMaybeWithAttachmentInfos(notification), notification)
builder
.setContentText(message)
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
}
}
private fun formatMessageMaybeWithAttachmentInfo(notification: Notification): String {
private fun formatMessageMaybeWithAttachmentInfos(notification: Notification): String {
val message = formatMessage(notification)
val attachment = notification.attachment ?: return message
val infos = if (attachment.size != null) {
val attachmentInfos = if (attachment.size != null) {
"${attachment.name}, ${formatBytes(attachment.size)}"
} else {
attachment.name
}
if (attachment.progress in 0..99) {
return context.getString(R.string.notification_popup_file_downloading, infos, attachment.progress, message)
return context.getString(R.string.notification_popup_file_downloading, attachmentInfos, attachment.progress, message)
}
if (attachment.progress == PROGRESS_DONE) {
return context.getString(R.string.notification_popup_file_download_successful, message, infos)
if (attachment.progress == ATTACHMENT_PROGRESS_DONE) {
return context.getString(R.string.notification_popup_file_download_successful, message, attachmentInfos)
}
if (attachment.progress == PROGRESS_FAILED) {
return context.getString(R.string.notification_popup_file_download_failed, message, infos)
if (attachment.progress == ATTACHMENT_PROGRESS_FAILED) {
return context.getString(R.string.notification_popup_file_download_failed, message, attachmentInfos)
}
return context.getString(R.string.notification_popup_file, message, attachmentInfos)
}
private fun maybeAppendActionErrors(message: String, notification: Notification): String {
val actionErrors = notification.actions
.orEmpty()
.mapNotNull { action -> action.error }
.joinToString("\n")
if (actionErrors.isEmpty()) {
return message
} else {
return "${message}\n\n${actionErrors}"
}
return context.getString(R.string.notification_popup_file, message, infos)
}
private fun setClickAction(builder: NotificationCompat.Builder, subscription: Subscription, notification: Notification) {
@ -135,7 +147,7 @@ class NotificationService(val context: Context) {
} else {
try {
val uri = Uri.parse(notification.click)
val viewIntent = PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW, uri), PendingIntent.FLAG_IMMUTABLE)
val viewIntent = PendingIntent.getActivity(context, Random().nextInt(), Intent(Intent.ACTION_VIEW, uri), PendingIntent.FLAG_IMMUTABLE)
builder.setContentIntent(viewIntent)
} catch (e: Exception) {
builder.setContentIntent(detailActivityIntent(subscription))
@ -159,7 +171,7 @@ class NotificationService(val context: Context) {
setDataAndType(contentUri, notification.attachment.type ?: "application/octet-stream") // Required for Android <= P
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val pendingIntent = PendingIntent.getActivity(context, Random().nextInt(), intent, PendingIntent.FLAG_IMMUTABLE)
builder.addAction(NotificationCompat.Action.Builder(0, context.getString(R.string.notification_popup_action_open), pendingIntent).build())
}
}
@ -169,18 +181,18 @@ class NotificationService(val context: Context) {
val intent = Intent(android.app.DownloadManager.ACTION_VIEW_DOWNLOADS).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val pendingIntent = PendingIntent.getActivity(context, Random().nextInt(), intent, PendingIntent.FLAG_IMMUTABLE)
builder.addAction(NotificationCompat.Action.Builder(0, context.getString(R.string.notification_popup_action_browse), pendingIntent).build())
}
}
private fun maybeAddDownloadAction(builder: NotificationCompat.Builder, notification: Notification) {
if (notification.attachment?.contentUri == null && listOf(PROGRESS_NONE, PROGRESS_FAILED).contains(notification.attachment?.progress)) {
if (notification.attachment?.contentUri == null && listOf(ATTACHMENT_PROGRESS_NONE, ATTACHMENT_PROGRESS_FAILED).contains(notification.attachment?.progress)) {
val intent = Intent(context, UserActionBroadcastReceiver::class.java).apply {
putExtra(BROADCAST_EXTRA_TYPE, BROADCAST_TYPE_DOWNLOAD_START)
putExtra(BROADCAST_EXTRA_NOTIFICATION_ID, notification.id)
}
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val pendingIntent = PendingIntent.getBroadcast(context, Random().nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
builder.addAction(NotificationCompat.Action.Builder(0, context.getString(R.string.notification_popup_action_download), pendingIntent).build())
}
}
@ -191,47 +203,51 @@ class NotificationService(val context: Context) {
putExtra(BROADCAST_EXTRA_TYPE, BROADCAST_TYPE_DOWNLOAD_CANCEL)
putExtra(BROADCAST_EXTRA_NOTIFICATION_ID, notification.id)
}
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val pendingIntent = PendingIntent.getBroadcast(context, Random().nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
builder.addAction(NotificationCompat.Action.Builder(0, context.getString(R.string.notification_popup_action_cancel), pendingIntent).build())
}
}
private fun maybeAddCustomActions(builder: NotificationCompat.Builder, notification: Notification) {
private fun maybeAddUserActions(builder: NotificationCompat.Builder, notification: Notification) {
notification.actions?.forEach { action ->
when (action.action.lowercase(Locale.getDefault())) {
ACTION_VIEW -> maybeAddViewUserAction(builder, action)
ACTION_HTTP -> maybeAddHttpUserAction(builder, notification, action)
ACTION_HTTP, ACTION_BROADCAST -> maybeAddHttpOrBroadcastUserAction(builder, notification, action)
}
}
}
private fun maybeAddViewUserAction(builder: NotificationCompat.Builder, action: Action) {
Log.d(TAG, "Adding user action $action")
try {
val url = action.url ?: return
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val pendingIntent = PendingIntent.getActivity(context, Random().nextInt(), 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)
}
}
private fun maybeAddHttpUserAction(builder: NotificationCompat.Builder, notification: Notification, action: Action) {
private fun maybeAddHttpOrBroadcastUserAction(builder: NotificationCompat.Builder, notification: Notification, action: Action) {
val intent = Intent(context, UserActionBroadcastReceiver::class.java).apply {
putExtra(BROADCAST_EXTRA_TYPE, BROADCAST_TYPE_USER_ACTION)
putExtra(BROADCAST_EXTRA_NOTIFICATION_ID, notification.id)
putExtra(BROADCAST_EXTRA_ACTION_ID, action.id)
}
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
builder.addAction(NotificationCompat.Action.Builder(0, action.label, pendingIntent).build())
val pendingIntent = PendingIntent.getBroadcast(context, Random().nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val label = when (action.progress) {
ACTION_PROGRESS_ONGOING -> action.label + ""
ACTION_PROGRESS_SUCCESS -> action.label + " ✔️"
ACTION_PROGRESS_FAILED -> action.label + " ❌️"
else -> action.label
}
builder.addAction(NotificationCompat.Action.Builder(0, label, pendingIntent).build())
}
class UserActionBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "Notification user action intent received: $intent")
val type = intent.getStringExtra(BROADCAST_EXTRA_TYPE) ?: return
val notificationId = intent.getStringExtra(BROADCAST_EXTRA_NOTIFICATION_ID) ?: return
when (type) {
@ -306,19 +322,19 @@ class NotificationService(val context: Context) {
}
companion object {
val ACTION_VIEW = "view"
val ACTION_HTTP = "http"
val ACTION_BROADCAST = "broadcast"
private const val TAG = "NtfyNotifService"
private const val BROADCAST_EXTRA_TYPE = "type"
private const val BROADCAST_EXTRA_NOTIFICATION_ID = "notificationId"
private const val BROADCAST_EXTRA_ACTION_ID = "action"
private const val BROADCAST_EXTRA_ACTION_JSON = "actionJson"
private const val BROADCAST_TYPE_DOWNLOAD_START = "io.heckel.ntfy.DOWNLOAD_ACTION_START"
private const val BROADCAST_TYPE_DOWNLOAD_CANCEL = "io.heckel.ntfy.DOWNLOAD_ACTION_CANCEL"
private const val BROADCAST_TYPE_USER_ACTION = "io.heckel.ntfy.USER_ACTION"
private const val ACTION_VIEW = "view"
private const val ACTION_HTTP = "http"
private const val BROADCAST_TYPE_USER_ACTION = "io.heckel.ntfy.USER_ACTION_RUN"
private const val CHANNEL_ID_MIN = "ntfy-min"
private const val CHANNEL_ID_LOW = "ntfy-low"

View file

@ -3,55 +3,99 @@ package io.heckel.ntfy.msg
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Action
import io.heckel.ntfy.db.*
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_BROADCAST
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_HTTP
import io.heckel.ntfy.util.Log
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.*
import java.util.concurrent.TimeUnit
class UserActionWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) {
private val client = OkHttpClient.Builder()
.callTimeout(15, TimeUnit.MINUTES) // Total timeout for entire request
.callTimeout(60, TimeUnit.SECONDS) // Total timeout for entire request
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
private val notifier = NotificationService(context)
private val broadcaster = BroadcastService(context)
private lateinit var repository: Repository
private lateinit var subscription: Subscription
private lateinit var notification: Notification
private lateinit var action: Action
override fun doWork(): Result {
if (context.applicationContext !is Application) return Result.failure()
val notificationId = inputData.getString(INPUT_DATA_NOTIFICATION_ID) ?: return Result.failure()
val actionId = inputData.getString(INPUT_DATA_ACTION_ID) ?: return Result.failure()
val app = context.applicationContext as Application
val notification = app.repository.getNotification(notificationId) ?: return Result.failure()
val action = notification.actions?.first { it.id == actionId } ?: return Result.failure()
repository = app.repository
notification = repository.getNotification(notificationId) ?: return Result.failure()
subscription = repository.getSubscription(notification.subscriptionId) ?: return Result.failure()
action = notification.actions?.first { it.id == actionId } ?: return Result.failure()
Log.d(TAG, "Executing action $action for notification $notification")
http(context, action)
try {
when (action.action) {
ACTION_HTTP -> performHttpAction(action)
ACTION_BROADCAST -> performBroadcastAction(action)
}
} catch (e: Exception) {
Log.w(TAG, "Error executing action: ${e.message}", e)
save(action.copy(
progress = ACTION_PROGRESS_FAILED,
error = context.getString(R.string.notification_popup_user_action_failed, action.label, e.message)
))
}
return Result.success()
}
private fun performHttpAction(action: Action) {
save(action.copy(progress = ACTION_PROGRESS_ONGOING, error = null))
fun http(context: Context, action: Action) { // FIXME Worker!
val url = action.url ?: return
val method = action.method ?: "GET"
val method = action.method ?: "POST" // (not GET, because POST as a default makes more sense!)
val body = action.body ?: ""
Log.d(TAG, "HTTP POST againt ${action.url}")
val request = Request.Builder()
val builder = Request.Builder()
.url(url)
.addHeader("User-Agent", ApiService.USER_AGENT)
.method(method, body.toRequestBody())
.build()
.addHeader("User-Agent", ApiService.USER_AGENT)
action.headers?.forEach { (key, value) ->
builder.addHeader(key, value)
}
val request = builder.build()
Log.d(TAG, "Executing HTTP request: ${method.uppercase(Locale.getDefault())} ${action.url}")
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
save(action.copy(progress = ACTION_PROGRESS_SUCCESS, error = null))
return
}
throw Exception("Unexpected server response ${response.code}")
throw Exception("HTTP ${response.code}")
}
}
private fun performBroadcastAction(action: Action) {
broadcaster.sendUserAction(action)
save(action.copy(progress = ACTION_PROGRESS_SUCCESS, error = null))
}
private fun save(newAction: Action) {
Log.d(TAG, "Updating action: $newAction")
val newActions = notification.actions?.map { a -> if (a.id == newAction.id) newAction else a }
val newNotification = notification.copy(actions = newActions)
action = newAction
notification = newNotification
notifier.update(subscription, notification)
repository.updateNotification(notification)
}
companion object {
const val INPUT_DATA_NOTIFICATION_ID = "notificationId"
const val INPUT_DATA_ACTION_ID = "actionId"

View file

@ -217,10 +217,10 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
private fun formatAttachmentDetails(context: Context, attachment: Attachment, exists: Boolean): String {
val name = attachment.name
val notYetDownloaded = !exists && attachment.progress == PROGRESS_NONE
val notYetDownloaded = !exists && attachment.progress == ATTACHMENT_PROGRESS_NONE
val downloading = !exists && attachment.progress in 0..99
val deleted = !exists && (attachment.progress == PROGRESS_DONE || attachment.progress == PROGRESS_DELETED)
val failed = !exists && attachment.progress == PROGRESS_FAILED
val deleted = !exists && (attachment.progress == ATTACHMENT_PROGRESS_DONE || attachment.progress == ATTACHMENT_PROGRESS_DELETED)
val failed = !exists && attachment.progress == ATTACHMENT_PROGRESS_FAILED
val expired = attachment.expires != null && attachment.expires < System.currentTimeMillis()/1000
val expires = attachment.expires != null && attachment.expires > System.currentTimeMillis()/1000
val infos = mutableListOf<String>()
@ -357,7 +357,7 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
if (!deleted) throw Exception("no rows deleted")
val newAttachment = attachment.copy(
contentUri = null,
progress = PROGRESS_DELETED
progress = ATTACHMENT_PROGRESS_DELETED
)
val newNotification = notification.copy(attachment = newAttachment)
GlobalScope.launch(Dispatchers.IO) {

View file

@ -5,7 +5,7 @@ import android.net.Uri
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.db.PROGRESS_DELETED
import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_DELETED
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.ui.DetailAdapter
import io.heckel.ntfy.util.Log
@ -48,7 +48,7 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx
}
val newAttachment = attachment.copy(
contentUri = null,
progress = PROGRESS_DELETED
progress = ATTACHMENT_PROGRESS_DELETED
)
val newNotification = notification.copy(attachment = newAttachment)
repository.updateNotification(newNotification)

View file

@ -220,6 +220,7 @@
<string name="notification_popup_file_downloading">Downloading %1$s, %2$d%%\n%3$s</string>
<string name="notification_popup_file_download_successful">%1$s\nFile: %2$s, downloaded</string>
<string name="notification_popup_file_download_failed">%1$s\nFile: %2$s, download failed</string>
<string name="notification_popup_user_action_failed">"%1$s" failed: %2$s</string>
<!-- Settings -->
<string name="settings_title">Settings</string>