From 529d147610d2ea574b4da2621808b7cd8621e5b4 Mon Sep 17 00:00:00 2001
From: "Jan C. Borchardt" <925062+jancborchardt@users.noreply.github.com>
Date: Wed, 3 Jun 2026 17:56:27 +0200
Subject: [PATCH] feat(chat): add "Copy message link" to message actions
Add a "Copy message link" option to the long-press message actions
bottom sheet. It copies a link in the same format the web client uses
({baseUrl}/call/{token}#message_{messageId}) to the clipboard, so the
link can be shared (e.g. by email) and opened in the browser, landing
on the referenced message.
Resolves: https://github.com/nextcloud/talk-android/issues/2361
Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Jan C. Borchardt <925062+jancborchardt@users.noreply.github.com>
---
.../com/nextcloud/talk/chat/ChatActivity.kt | 22 +++++++++++++++++++
.../talk/chat/ui/MessageActionsBottomSheet.kt | 20 +++++++++++++++++
app/src/main/res/values/strings.xml | 1 +
3 files changed, 43 insertions(+)
diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
index 43da92f33f..c0dec670b9 100644
--- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
@@ -948,6 +948,7 @@ class ChatActivity :
onForward = { forwardMessage(msg) },
onEdit = { messageInputViewModel.edit(msg) },
onCopy = { copyMessage(msg) },
+ onCopyMessageLink = { copyMessageLink(msg) },
onMarkAsUnread = { markAsUnread(msg) },
onRemind = { remindMeLater(msg) },
onPin = { pinMessage(msg) },
@@ -3512,6 +3513,27 @@ class ChatActivity :
clipboardManager.setPrimaryClip(clipData)
}
+ fun copyMessageLink(message: ChatMessage) {
+ val baseUrl = conversationUser?.baseUrl
+ if (baseUrl.isNullOrEmpty()) {
+ Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
+ return
+ }
+
+ // Matches the web client link format: {baseUrl}/call/{token}#message_{messageId}
+ val messageLink = "$baseUrl/call/$roomToken#message_${message.jsonMessageId}"
+
+ val clipboardManager =
+ getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
+ val clipData = ClipData.newPlainText(
+ resources?.getString(R.string.nc_app_product_name),
+ messageLink
+ )
+ clipboardManager.setPrimaryClip(clipData)
+
+ Snackbar.make(binding.root, R.string.nc_common_copy_success, Snackbar.LENGTH_SHORT).show()
+ }
+
fun translateMessage(message: ChatMessage?) {
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_TRANSLATE_MESSAGE, message?.getRichText())
diff --git a/app/src/main/java/com/nextcloud/talk/chat/ui/MessageActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/talk/chat/ui/MessageActionsBottomSheet.kt
index 8ec37b1f7f..7f0c4f8216 100644
--- a/app/src/main/java/com/nextcloud/talk/chat/ui/MessageActionsBottomSheet.kt
+++ b/app/src/main/java/com/nextcloud/talk/chat/ui/MessageActionsBottomSheet.kt
@@ -129,6 +129,7 @@ data class MessageActionsState(
val showForward: Boolean,
val showEdit: Boolean,
val showCopy: Boolean,
+ val showCopyMessageLink: Boolean,
val showMarkAsUnread: Boolean,
val showRemind: Boolean,
val showPin: Boolean,
@@ -242,6 +243,8 @@ internal fun buildMessageActionsState(
isOnline,
showEdit = isMessageEditable,
showCopy = !message.isDeleted,
+ showCopyMessageLink = !message.isDeleted &&
+ ChatMessage.MessageType.SYSTEM_MESSAGE != messageType,
showMarkAsUnread = hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.CHAT_READ_MARKER) &&
ChatMessage.MessageType.SYSTEM_MESSAGE != messageType &&
isOnline,
@@ -279,6 +282,7 @@ fun MessageActionsBottomSheet(
onForward: () -> Unit,
onEdit: () -> Unit,
onCopy: () -> Unit,
+ onCopyMessageLink: () -> Unit,
onMarkAsUnread: () -> Unit,
onRemind: () -> Unit,
onPin: () -> Unit,
@@ -306,6 +310,7 @@ fun MessageActionsBottomSheet(
onForward = onForward,
onEdit = onEdit,
onCopy = onCopy,
+ onCopyMessageLink = onCopyMessageLink,
onMarkAsUnread = onMarkAsUnread,
onRemind = onRemind,
onPin = onPin,
@@ -332,6 +337,7 @@ internal fun MessageActionsSheetContent(
onForward: () -> Unit,
onEdit: () -> Unit,
onCopy: () -> Unit,
+ onCopyMessageLink: () -> Unit,
onMarkAsUnread: () -> Unit,
onRemind: () -> Unit,
onPin: () -> Unit,
@@ -436,6 +442,16 @@ internal fun MessageActionsSheetContent(
}
)
}
+ if (actionsState.showCopyMessageLink) {
+ MessageActionItem(
+ iconRes = R.drawable.ic_link,
+ text = stringResource(R.string.nc_copy_message_link),
+ onClick = {
+ onCopyMessageLink()
+ onDismiss()
+ }
+ )
+ }
if (actionsState.showMarkAsUnread) {
MessageActionItem(
iconRes = R.drawable.ic_mark_chat_unread_24px,
@@ -808,6 +824,7 @@ private fun PreviewMessageActionsSheetContent() {
showForward = true,
showEdit = true,
showCopy = true,
+ showCopyMessageLink = true,
showMarkAsUnread = true,
showRemind = true,
showPin = true,
@@ -830,6 +847,7 @@ private fun PreviewMessageActionsSheetContent() {
onForward = {},
onEdit = {},
onCopy = {},
+ onCopyMessageLink = {},
onMarkAsUnread = {},
onRemind = {},
onPin = {},
@@ -864,6 +882,7 @@ private fun PreviewMessageActionsSheetPinned() {
showForward = false,
showEdit = false,
showCopy = true,
+ showCopyMessageLink = true,
showMarkAsUnread = false,
showRemind = false,
showPin = true,
@@ -882,6 +901,7 @@ private fun PreviewMessageActionsSheetPinned() {
onForward = {},
onEdit = {},
onCopy = {},
+ onCopyMessageLink = {},
onMarkAsUnread = {},
onRemind = {},
onPin = {},
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index addf50e603..1344ea460b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -562,6 +562,7 @@ How to translate with transifex:
Copy
+ Copy message link
Forward
Reply
Reply privately