diff --git a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt index 9be9bc37..2dafa839 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -156,6 +156,7 @@ class Backuper(val context: Context) { body = a.body, intent = a.intent, extras = a.extras, + value = a.value, progress = a.progress, error = a.error ) @@ -316,6 +317,7 @@ class Backuper(val context: Context) { body = a.body, intent = a.intent, extras = a.extras, + value = a.value, progress = a.progress, error = a.error ) @@ -459,7 +461,7 @@ data class Notification( data class Action( val id: String, // Synthetic ID to identify result, and easily pass via Broadcast and WorkManager - val action: String, // "view", "http" or "broadcast" + val action: String, // "view", "http", "broadcast", or "copy" val label: String, val clear: Boolean?, // clear notification after successful execution val url: String?, // used in "view" and "http" actions @@ -468,6 +470,7 @@ data class Action( val body: String?, // used in "http" action val intent: String?, // used in "broadcast" action val extras: Map?, // used in "broadcast" action + val value: String? = null, // used in "copy" action val progress: Int?, // used to indicate progress in popup val error: String? // used to indicate errors in popup ) diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index fa3b7187..16ab2231 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -222,7 +222,7 @@ data class Icon( @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, // "view", "http" or "broadcast" + @ColumnInfo(name = "action") val action: String, // "view", "http", "broadcast", or "copy" @ColumnInfo(name = "label") val label: String, @ColumnInfo(name = "clear") val clear: Boolean?, // clear notification after successful execution @ColumnInfo(name = "url") val url: String?, // used in "view" and "http" actions @@ -231,6 +231,7 @@ data class Action( @ColumnInfo(name = "body") val body: String?, // used in "http" action @ColumnInfo(name = "intent") val intent: String?, // used in "broadcast" action @ColumnInfo(name = "extras") val extras: Map?, // used in "broadcast" action + @ColumnInfo(name = "value") val value: String?, // used in "copy" 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 ) diff --git a/app/src/main/java/io/heckel/ntfy/msg/Message.kt b/app/src/main/java/io/heckel/ntfy/msg/Message.kt index 8aa62b5f..d20720a7 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/Message.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/Message.kt @@ -37,7 +37,7 @@ data class MessageAttachment( data class MessageAction( val id: String, val action: String, - val label: String, // "view", "broadcast" or "http" + val label: String, // "view", "broadcast", "http", or "copy" val clear: Boolean?, // clear notification after successful execution val url: String?, // used in "view" and "http" actions val method: String?, // used in "http" action, default is POST (!) @@ -45,6 +45,7 @@ data class MessageAction( val body: String?, // used in "http" action val intent: String?, // used in "broadcast" action val extras: Map?, // used in "broadcast" action + val value: String?, // used in "copy" action ) const val MESSAGE_ENCODING_BASE64 = "base64" diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt index 14ff71ac..25023295 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt @@ -48,6 +48,7 @@ class NotificationParser { body = a.body, intent = a.intent, extras = a.extras, + value = a.value, progress = null, error = null ) @@ -96,6 +97,7 @@ class NotificationParser { body = a.body, intent = a.intent, extras = a.extras, + value = a.value, progress = null, error = null ) diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt index 1d621ecc..ad489dde 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -267,7 +267,7 @@ class NotificationService(val context: Context) { addViewUserActionWithoutClear(builder, action) } } else { - addHttpOrBroadcastUserAction(builder, notification, action) + addHttpBroadcastOrCopyUserAction(builder, notification, action) } } } @@ -310,7 +310,7 @@ class NotificationService(val context: Context) { } } - private fun addHttpOrBroadcastUserAction(builder: NotificationCompat.Builder, notification: Notification, action: Action) { + private fun addHttpBroadcastOrCopyUserAction(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) @@ -323,7 +323,7 @@ class NotificationService(val context: Context) { /** * Receives the broadcast from - * - the "http" and "broadcast" action button (the "view" action is handled differently) + * - the "http", "broadcast", and "copy" action button (the "view" action is handled differently) * - the "download"/"cancel" action button * * Then queues a Worker via WorkManager to execute the action in the background @@ -523,6 +523,7 @@ class NotificationService(val context: Context) { const val ACTION_VIEW = "view" const val ACTION_HTTP = "http" const val ACTION_BROADCAST = "broadcast" + const val ACTION_COPY = "copy" const val BROADCAST_EXTRA_TYPE = "type" const val BROADCAST_EXTRA_NOTIFICATION_ID = "notificationId" diff --git a/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt b/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt index a31c4015..41a11a42 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt @@ -13,9 +13,11 @@ import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_BROADCAST +import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_COPY import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_HTTP import io.heckel.ntfy.util.HttpUtil import io.heckel.ntfy.util.Log +import io.heckel.ntfy.util.copyToClipboard import io.heckel.ntfy.util.extractBaseUrl import okhttp3.RequestBody.Companion.toRequestBody import java.util.Locale @@ -45,6 +47,7 @@ class UserActionWorker(private val context: Context, params: WorkerParameters) : // ACTION_VIEW is not handled here. It's handled in the NotificationService and DetailAdapter. ACTION_BROADCAST -> performBroadcastAction(action) ACTION_HTTP -> performHttpAction(action) + ACTION_COPY -> performCopyAction(action) } } catch (e: Exception) { Log.w(TAG, "Error executing action: ${e.message}", e) @@ -56,6 +59,15 @@ class UserActionWorker(private val context: Context, params: WorkerParameters) : return Result.success() } + private fun performCopyAction(action: Action) { + val value = action.value ?: return + copyToClipboard(context, action.label, value) + if (action.clear == true) { + notifier.cancel(notification) + repository.markAsReadBySequenceId(subscription.id, notification.sequenceId) + } + } + private fun performBroadcastAction(action: Action) { broadcaster.sendUserAction(action) if (action.clear == true) { diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt index 67f8bd9c..dd2187f8 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt @@ -32,6 +32,7 @@ import io.heckel.ntfy.msg.DownloadAttachmentWorker import io.heckel.ntfy.msg.DownloadManager import io.heckel.ntfy.msg.DownloadType import io.heckel.ntfy.msg.NotificationService +import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_COPY import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW import io.heckel.ntfy.util.* import io.noties.markwon.Markwon @@ -515,6 +516,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope: private fun runAction(context: Context, notification: Notification, action: Action): Boolean { when (action.action) { ACTION_VIEW -> runViewAction(context, action) + ACTION_COPY -> runCopyAction(context, action) else -> runOtherUserAction(context, notification, action) } return true @@ -536,6 +538,11 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope: } } + private fun runCopyAction(context: Context, action: Action) { + val value = action.value ?: return + copyToClipboard(context, action.label, value) + } + private fun runOtherUserAction(context: Context, notification: Notification, action: Action) { val intent = Intent(context, NotificationService.UserActionBroadcastReceiver::class.java).apply { putExtra(NotificationService.BROADCAST_EXTRA_TYPE, NotificationService.BROADCAST_TYPE_USER_ACTION) diff --git a/fastlane/metadata/android/en-US/changelog/NEXT.txt b/fastlane/metadata/android/en-US/changelog/NEXT.txt index 428ece57..8c91e5e5 100644 --- a/fastlane/metadata/android/en-US/changelog/NEXT.txt +++ b/fastlane/metadata/android/en-US/changelog/NEXT.txt @@ -3,6 +3,7 @@ Features: * Add "reconnecting to N topics ..." to foreground notification (#1101, thanks to @milosivanovic for reporting) * Default server dialog with full-screen UI and stricter URL validation (ntfy-android#158) * Show last notification time for UnifiedPush subscriptions (#1230, #1454, thanks to @Tealk and @user4andre for reporting) +* Support "copy" action button to copy a value to the clipboard (#1364, thanks to @SudoWatson for reporting) Bug fixes + maintenance: * Fix clear=true on action buttons not marking notification as read (#1029, thanks to @ElFishi for reporting)