From cd7e0c9dcd03a7418db85c5086f892c69dee7e2d Mon Sep 17 00:00:00 2001 From: bling-yshs <377178599@qq.com> Date: Fri, 3 Oct 2025 08:57:03 +0800 Subject: [PATCH] feat(clipboard): read HTML from clipboard as plain text on Android --- .../feat-clipboard-android-html-support.md | 4 ++ .../android/src/main/java/ClipboardPlugin.kt | 58 +++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 .changes/feat-clipboard-android-html-support.md diff --git a/.changes/feat-clipboard-android-html-support.md b/.changes/feat-clipboard-android-html-support.md new file mode 100644 index 0000000000..c33896db00 --- /dev/null +++ b/.changes/feat-clipboard-android-html-support.md @@ -0,0 +1,4 @@ +--- +'@tauri-apps/plugin-clipboard-manager': 'minor' +--- +On Android, the `readText` command now supports reading HTML content from the clipboard by converting it to plain text. \ No newline at end of file diff --git a/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt b/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt index ebb931b409..f746692377 100644 --- a/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt +++ b/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt @@ -10,6 +10,7 @@ import android.content.ClipDescription import android.content.ClipboardManager import android.content.Context import android.os.Build +import android.text.Html import app.tauri.annotation.Command import app.tauri.annotation.InvokeArg import app.tauri.annotation.TauriPlugin @@ -107,25 +108,58 @@ class ClipboardPlugin(private val activity: Activity) : Plugin(activity) { invoke.resolve() } + @Command fun readText(invoke: Invoke) { - val data = if (manager.hasPrimaryClip()) { - if (manager.primaryClipDescription?.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) == true) { - val item: ClipData.Item = manager.primaryClip!!.getItemAt(0) - val data = ReadClipData.PlainText() - data.text = item.text.toString() - data - } else { - // TODO - invoke.reject("Clipboard content reader not implemented") + // Use .let for safely handling nullable primaryClip + val data = manager.primaryClip?.let { clip -> + // Ensure the clipboard actually has something + if (clip.itemCount == 0) { + invoke.reject("Clipboard is empty") return } + + val item = clip.getItemAt(0) + val description = clip.description + + // Use a when statement to gracefully handle different MIME types. + // HTML is prioritized as it often contains richer information. + when { + // Case 1: Clipboard contains HTML text + description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML) -> { + val htmlText = item.htmlText + // Convert HTML to plain text, also handling the case where htmlText might be null + val plainText = if (htmlText != null) { + // Use different fromHtml methods depending on the Android version, which is a best practice + Html.fromHtml(htmlText, Html.FROM_HTML_MODE_COMPACT).toString() + } else { + // If htmlText is null, fall back to plain text + item.text?.toString() ?: "" + } + ReadClipData.PlainText().apply { text = plainText } + } + + // Case 2: Clipboard contains only plain text + description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) -> { + val plainText = item.text?.toString() ?: "" + ReadClipData.PlainText().apply { text = plainText } + } + + // Case 3: The content is not a text type we can handle (e.g., an image or a URI) + else -> { + invoke.reject("Clipboard content is not plain text or HTML") + return + } + } + } + + // The final call is determined by whether data is null or not + if (data != null) { + invoke.resolveObject(data) } else { + // if manager.primaryClip is null, data will be null as well invoke.reject("Clipboard is empty") - return } - - invoke.resolveObject(data) } @Command