From d55f311f338790d27d6402a2f0ba81c0c8131f52 Mon Sep 17 00:00:00 2001 From: Vladimir Pankratov Date: Tue, 30 Dec 2025 19:22:53 +0100 Subject: [PATCH 01/26] Add share target support for Android --- README.md | 72 +++++++++ android/src/main/java/SharePlugin.kt | 153 ++++++++++++++++++ build.rs | 9 +- .../android/app/src/main/AndroidManifest.xml | 35 ++++ examples/share-demo/src/routes/+page.svelte | 88 +++++++++- guest-js/index.ts | 70 +++++++- .../clear_pending_shared_content.toml | 13 ++ .../commands/get_pending_shared_content.toml | 13 ++ .../commands/register_listener.toml | 13 ++ .../commands/remove_listener.toml | 13 ++ permissions/autogenerated/reference.md | 108 +++++++++++++ permissions/default.toml | 4 + permissions/schemas/schema.json | 52 +++++- src/commands.rs | 16 ++ src/desktop.rs | 8 + src/lib.rs | 4 +- src/macos.rs | 8 + src/mobile.rs | 12 ++ src/models.rs | 31 +++- src/windows.rs | 8 + 20 files changed, 723 insertions(+), 7 deletions(-) create mode 100644 permissions/autogenerated/commands/clear_pending_shared_content.toml create mode 100644 permissions/autogenerated/commands/get_pending_shared_content.toml create mode 100644 permissions/autogenerated/commands/register_listener.toml create mode 100644 permissions/autogenerated/commands/remove_listener.toml diff --git a/README.md b/README.md index 6b46b47..caf5122 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,78 @@ await shareFile('file:///path/to/document.pdf', { }); ``` +### Receiving Shared Content (Share Target) + +Your app can receive content shared from other apps: + +```javascript +import { + getPendingSharedContent, + clearPendingSharedContent, + onSharedContent +} from "@choochmeque/tauri-plugin-sharekit-api"; + +// Check for shared content on app startup (cold start) +const content = await getPendingSharedContent(); +if (content) { + if (content.type === 'text') { + console.log('Received text:', content.text); + } else if (content.type === 'files') { + for (const file of content.files) { + console.log('Received file:', file.name, file.path); + } + } + await clearPendingSharedContent(); +} + +// Listen for shares while app is running (warm start) +const unlisten = await onSharedContent((content) => { + console.log('Received:', content); +}); +``` + +## Platform Setup + +### Android + +To receive shared content on Android, add intent filters to your `AndroidManifest.xml`: + +`src-tauri/gen/android/app/src/main/AndroidManifest.xml` + +Add these intent filters inside your `` tag: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +You can customize the `mimeType` values to only accept specific file types. + ## Contributing PRs accepted. Please make sure to read the Contributing Guide before making a pull request. diff --git a/android/src/main/java/SharePlugin.kt b/android/src/main/java/SharePlugin.kt index 5666604..ce7cef9 100644 --- a/android/src/main/java/SharePlugin.kt +++ b/android/src/main/java/SharePlugin.kt @@ -6,17 +6,22 @@ import android.content.pm.PackageManager import android.os.Build import android.webkit.WebView import android.net.Uri +import android.webkit.MimeTypeMap import androidx.activity.result.ActivityResult import app.tauri.annotation.ActivityCallback import app.tauri.annotation.Command import app.tauri.annotation.InvokeArg import app.tauri.annotation.TauriPlugin import app.tauri.plugin.Invoke +import app.tauri.plugin.JSObject import app.tauri.plugin.Plugin import androidx.core.content.FileProvider +import org.json.JSONArray +import org.json.JSONObject import java.io.File import java.io.FileInputStream import java.io.FileOutputStream +import java.util.UUID @InvokeArg class ShareTextOptions { @@ -34,6 +39,154 @@ class ShareFileOptions { @TauriPlugin class SharePlugin(private val activity: Activity): Plugin(activity) { + private var pendingSharedContent: JSObject? = null + + override fun load(webView: WebView) { + super.load(webView) + handleIntent(activity.intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handleIntent(intent) + } + + private fun handleIntent(intent: Intent?) { + if (intent == null) return + + when (intent.action) { + Intent.ACTION_SEND -> handleSingleShare(intent) + Intent.ACTION_SEND_MULTIPLE -> handleMultipleShare(intent) + } + + if (pendingSharedContent != null) { + trigger("sharedContent", pendingSharedContent!!) + } + } + + private fun handleSingleShare(intent: Intent) { + val type = intent.type ?: return + + if (type.startsWith("text/")) { + val text = intent.getStringExtra(Intent.EXTRA_TEXT) + if (text != null) { + pendingSharedContent = JSObject().apply { + put("type", "text") + put("text", text) + } + } + } else { + val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(Intent.EXTRA_STREAM) + } + + if (uri != null) { + val file = copyUriToCache(uri) + if (file != null) { + val filesArray = JSONArray() + filesArray.put(file) + pendingSharedContent = JSObject().apply { + put("type", "files") + put("files", filesArray) + } + } + } + } + } + + private fun handleMultipleShare(intent: Intent) { + val uris = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) + } + + if (uris != null && uris.isNotEmpty()) { + val filesArray = JSONArray() + for (uri in uris) { + val file = copyUriToCache(uri) + if (file != null) { + filesArray.put(file) + } + } + if (filesArray.length() > 0) { + pendingSharedContent = JSObject().apply { + put("type", "files") + put("files", filesArray) + } + } + } + } + + private fun copyUriToCache(uri: Uri): JSONObject? { + return try { + val contentResolver = activity.contentResolver + val mimeType = contentResolver.getType(uri) + + var fileName = getFileName(uri) + if (fileName == null) { + val ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "" + fileName = "${UUID.randomUUID()}.$ext" + } + + val cacheDir = File(activity.cacheDir, "shared_files") + cacheDir.mkdirs() + val destFile = File(cacheDir, fileName) + + contentResolver.openInputStream(uri)?.use { input -> + FileOutputStream(destFile).use { output -> + input.copyTo(output) + } + } + + JSONObject().apply { + put("path", destFile.absolutePath) + put("name", fileName) + if (mimeType != null) put("mimeType", mimeType) + put("size", destFile.length()) + } + } catch (e: Exception) { + null + } + } + + private fun getFileName(uri: Uri): String? { + var result: String? = null + if (uri.scheme == "content") { + activity.contentResolver.query(uri, null, null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) { + val nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME) + if (nameIndex >= 0) { + result = cursor.getString(nameIndex) + } + } + } + } + if (result == null) { + result = uri.path?.substringAfterLast('/') + } + return result + } + + @Command + fun getPendingSharedContent(invoke: Invoke) { + if (pendingSharedContent != null) { + invoke.resolve(pendingSharedContent!!) + } else { + invoke.resolve() + } + } + + @Command + fun clearPendingSharedContent(invoke: Invoke) { + pendingSharedContent = null + invoke.resolve() + } + /** * Open the native sharing interface to share some text */ diff --git a/build.rs b/build.rs index e9aa981..080d778 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,11 @@ -const COMMANDS: &[&str] = &["share_text", "share_file"]; +const COMMANDS: &[&str] = &[ + "register_listener", + "remove_listener", + "share_text", + "share_file", + "get_pending_shared_content", + "clear_pending_shared_content", +]; fn main() { tauri_plugin::Builder::new(COMMANDS) diff --git a/examples/share-demo/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/examples/share-demo/src-tauri/gen/android/app/src/main/AndroidManifest.xml index 042abff..bd7a89e 100644 --- a/examples/share-demo/src-tauri/gen/android/app/src/main/AndroidManifest.xml +++ b/examples/share-demo/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -22,6 +22,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - import { shareText, shareFile } from "@choochmeque/tauri-plugin-sharekit-api"; + import { onMount } from "svelte"; + import { + shareText, + shareFile, + getPendingSharedContent, + clearPendingSharedContent, + onSharedContent, + type SharedContent + } from "@choochmeque/tauri-plugin-sharekit-api"; // Text sharing state let text = $state("Hello from Tauri ShareKit!"); @@ -12,6 +20,37 @@ let fileTitle = $state(""); let fileStatus = $state(""); + // Received content state + let receivedContent = $state(null); + + onMount(async () => { + // Check for content from cold start + await checkForSharedContent(); + + // Listen for content from warm start + const listener = await onSharedContent((content) => { + receivedContent = content; + }); + + return () => listener.unregister(); + }); + + async function checkForSharedContent() { + try { + const content = await getPendingSharedContent(); + if (content) { + receivedContent = content; + } + } catch (e) { + console.error("Failed to get shared content:", e); + } + } + + async function handleClearSharedContent() { + await clearPendingSharedContent(); + receivedContent = null; + } + async function handleShareText() { textStatus = "Sharing..."; try { @@ -44,6 +83,29 @@

ShareKit Demo

+ {#if receivedContent} +
+

Received Content

+

Type: {receivedContent.type}

+ {#if receivedContent.type === "text" && receivedContent.text} +

Text: {receivedContent.text}

+ {:else if receivedContent.type === "files" && receivedContent.files} +

Files:

+
    + {#each receivedContent.files as file} +
  • + {file.name} +
    Path: {file.path} + {#if file.mimeType}
    MIME: {file.mimeType}{/if} + {#if file.size}
    Size: {file.size} bytes{/if} +
  • + {/each} +
+ {/if} + +
+ {/if} +

Share Text

@@ -120,6 +182,25 @@ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } + .card.received { + border: 2px solid #396cd8; + background: #e8f0fe; + } + + .card.received ul { + margin: 0.5rem 0; + padding-left: 1.5rem; + } + + .card.received li { + margin-bottom: 0.5rem; + word-break: break-all; + } + + .card.received p { + word-break: break-all; + } + .form-group { margin-bottom: 1rem; } @@ -191,6 +272,11 @@ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } + .card.received { + background: #2a3a5c; + border-color: #5a8cfa; + } + input, textarea { background: #2f2f2f; border-color: #555; diff --git a/guest-js/index.ts b/guest-js/index.ts index adfb113..0436755 100644 --- a/guest-js/index.ts +++ b/guest-js/index.ts @@ -1,4 +1,4 @@ -import { invoke } from "@tauri-apps/api/core"; +import { invoke, addPluginListener, PluginListener } from "@tauri-apps/api/core"; export interface ShareTextOptions { // Android only @@ -10,6 +10,19 @@ export interface ShareFileOptions { title?: string; } +export interface SharedFile { + path: string; + name: string; + mimeType?: string; + size?: number; +} + +export interface SharedContent { + type: "text" | "files"; + text?: string; + files?: SharedFile[]; +} + /** * Opens the native sharing interface to share the specified text. * @@ -54,3 +67,58 @@ export async function shareFile( ...options, }); } + +/** + * Gets content shared to this app from other apps. + * Call this on app startup to check if the app was launched via share. + * + * ```javascript + * import { getPendingSharedContent, clearPendingSharedContent } from "@choochmeque/tauri-plugin-sharekit-api"; + * + * const content = await getPendingSharedContent(); + * if (content) { + * if (content.type === 'text') { + * console.log('Received text:', content.text); + * } else if (content.type === 'files') { + * console.log('Received files:', content.files); + * } + * await clearPendingSharedContent(); + * } + * ``` + * @returns The shared content, or null if no content was shared + */ +export async function getPendingSharedContent(): Promise { + return await invoke( + "plugin:sharekit|get_pending_shared_content", + ); +} + +/** + * Clears the pending shared content after it has been processed. + * Call this after handling shared content to prevent it from being + * returned again on subsequent calls to getPendingSharedContent(). + */ +export async function clearPendingSharedContent(): Promise { + await invoke("plugin:sharekit|clear_pending_shared_content"); +} + +/** + * Listens for content shared to this app while it's running. + * Use this for warm start scenarios (app already running when share happens). + * + * ```javascript + * import { onSharedContent } from "@choochmeque/tauri-plugin-sharekit-api"; + * + * const unlisten = await onSharedContent((content) => { + * console.log('Received:', content); + * }); + * + * // Later, to stop listening: + * unlisten.unregister(); + * ``` + */ +export async function onSharedContent( + handler: (content: SharedContent) => void, +): Promise { + return await addPluginListener("sharekit", "sharedContent", handler); +} diff --git a/permissions/autogenerated/commands/clear_pending_shared_content.toml b/permissions/autogenerated/commands/clear_pending_shared_content.toml new file mode 100644 index 0000000..c682b9e --- /dev/null +++ b/permissions/autogenerated/commands/clear_pending_shared_content.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-clear-pending-shared-content" +description = "Enables the clear_pending_shared_content command without any pre-configured scope." +commands.allow = ["clear_pending_shared_content"] + +[[permission]] +identifier = "deny-clear-pending-shared-content" +description = "Denies the clear_pending_shared_content command without any pre-configured scope." +commands.deny = ["clear_pending_shared_content"] diff --git a/permissions/autogenerated/commands/get_pending_shared_content.toml b/permissions/autogenerated/commands/get_pending_shared_content.toml new file mode 100644 index 0000000..b8490be --- /dev/null +++ b/permissions/autogenerated/commands/get_pending_shared_content.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-pending-shared-content" +description = "Enables the get_pending_shared_content command without any pre-configured scope." +commands.allow = ["get_pending_shared_content"] + +[[permission]] +identifier = "deny-get-pending-shared-content" +description = "Denies the get_pending_shared_content command without any pre-configured scope." +commands.deny = ["get_pending_shared_content"] diff --git a/permissions/autogenerated/commands/register_listener.toml b/permissions/autogenerated/commands/register_listener.toml new file mode 100644 index 0000000..48363c0 --- /dev/null +++ b/permissions/autogenerated/commands/register_listener.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-register-listener" +description = "Enables the register_listener command without any pre-configured scope." +commands.allow = ["register_listener"] + +[[permission]] +identifier = "deny-register-listener" +description = "Denies the register_listener command without any pre-configured scope." +commands.deny = ["register_listener"] diff --git a/permissions/autogenerated/commands/remove_listener.toml b/permissions/autogenerated/commands/remove_listener.toml new file mode 100644 index 0000000..9f315e5 --- /dev/null +++ b/permissions/autogenerated/commands/remove_listener.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-remove-listener" +description = "Enables the remove_listener command without any pre-configured scope." +commands.allow = ["remove_listener"] + +[[permission]] +identifier = "deny-remove-listener" +description = "Denies the remove_listener command without any pre-configured scope." +commands.deny = ["remove_listener"] diff --git a/permissions/autogenerated/reference.md b/permissions/autogenerated/reference.md index efa69d4..053be44 100644 --- a/permissions/autogenerated/reference.md +++ b/permissions/autogenerated/reference.md @@ -9,8 +9,12 @@ It allows acccess to all share commands. #### This default permission set includes the following: +- `allow-register-listener` +- `allow-remove-listener` - `allow-share-text` - `allow-share-file` +- `allow-get-pending-shared-content` +- `allow-clear-pending-shared-content` ## Permission Table @@ -21,6 +25,110 @@ It allows acccess to all share commands. + + + +`sharekit:allow-clear-pending-shared-content` + + + + +Enables the clear_pending_shared_content command without any pre-configured scope. + + + + + + + +`sharekit:deny-clear-pending-shared-content` + + + + +Denies the clear_pending_shared_content command without any pre-configured scope. + + + + + + + +`sharekit:allow-get-pending-shared-content` + + + + +Enables the get_pending_shared_content command without any pre-configured scope. + + + + + + + +`sharekit:deny-get-pending-shared-content` + + + + +Denies the get_pending_shared_content command without any pre-configured scope. + + + + + + + +`sharekit:allow-register-listener` + + + + +Enables the register_listener command without any pre-configured scope. + + + + + + + +`sharekit:deny-register-listener` + + + + +Denies the register_listener command without any pre-configured scope. + + + + + + + +`sharekit:allow-remove-listener` + + + + +Enables the remove_listener command without any pre-configured scope. + + + + + + + +`sharekit:deny-remove-listener` + + + + +Denies the remove_listener command without any pre-configured scope. + + + + diff --git a/permissions/default.toml b/permissions/default.toml index 6473e2d..b848821 100644 --- a/permissions/default.toml +++ b/permissions/default.toml @@ -11,6 +11,10 @@ It allows acccess to all share commands. """ permissions = [ + "allow-register-listener", + "allow-remove-listener", "allow-share-text", "allow-share-file", + "allow-get-pending-shared-content", + "allow-clear-pending-shared-content", ] diff --git a/permissions/schemas/schema.json b/permissions/schemas/schema.json index 0ea2196..b7f774d 100644 --- a/permissions/schemas/schema.json +++ b/permissions/schemas/schema.json @@ -294,6 +294,54 @@ "PermissionKind": { "type": "string", "oneOf": [ + { + "description": "Enables the clear_pending_shared_content command without any pre-configured scope.", + "type": "string", + "const": "allow-clear-pending-shared-content", + "markdownDescription": "Enables the clear_pending_shared_content command without any pre-configured scope." + }, + { + "description": "Denies the clear_pending_shared_content command without any pre-configured scope.", + "type": "string", + "const": "deny-clear-pending-shared-content", + "markdownDescription": "Denies the clear_pending_shared_content command without any pre-configured scope." + }, + { + "description": "Enables the get_pending_shared_content command without any pre-configured scope.", + "type": "string", + "const": "allow-get-pending-shared-content", + "markdownDescription": "Enables the get_pending_shared_content command without any pre-configured scope." + }, + { + "description": "Denies the get_pending_shared_content command without any pre-configured scope.", + "type": "string", + "const": "deny-get-pending-shared-content", + "markdownDescription": "Denies the get_pending_shared_content command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, { "description": "Enables the share_file command without any pre-configured scope.", "type": "string", @@ -319,10 +367,10 @@ "markdownDescription": "Denies the share_text command without any pre-configured scope." }, { - "description": "This permission set configures which\nshare features are by default exposed.\n\n#### Granted Permissions\n\nIt allows acccess to all share commands.\n\n\n#### This default permission set includes:\n\n- `allow-share-text`\n- `allow-share-file`", + "description": "This permission set configures which\nshare features are by default exposed.\n\n#### Granted Permissions\n\nIt allows acccess to all share commands.\n\n\n#### This default permission set includes:\n\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-share-text`\n- `allow-share-file`\n- `allow-get-pending-shared-content`\n- `allow-clear-pending-shared-content`", "type": "string", "const": "default", - "markdownDescription": "This permission set configures which\nshare features are by default exposed.\n\n#### Granted Permissions\n\nIt allows acccess to all share commands.\n\n\n#### This default permission set includes:\n\n- `allow-share-text`\n- `allow-share-file`" + "markdownDescription": "This permission set configures which\nshare features are by default exposed.\n\n#### Granted Permissions\n\nIt allows acccess to all share commands.\n\n\n#### This default permission set includes:\n\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-share-text`\n- `allow-share-file`\n- `allow-get-pending-shared-content`\n- `allow-clear-pending-shared-content`" } ] } diff --git a/src/commands.rs b/src/commands.rs index 9d6046e..b2c55f2 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -25,3 +25,19 @@ pub async fn share_file( .share_file(url, ShareFileOptions { mime_type, title }) .map_err(|e| e.to_string()) } + +#[command] +pub async fn get_pending_shared_content( + app: AppHandle, +) -> Result, String> { + app.share() + .get_pending_shared_content() + .map_err(|e| e.to_string()) +} + +#[command] +pub async fn clear_pending_shared_content(app: AppHandle) -> Result<(), String> { + app.share() + .clear_pending_shared_content() + .map_err(|e| e.to_string()) +} diff --git a/src/desktop.rs b/src/desktop.rs index 5d927ad..acdd971 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -21,4 +21,12 @@ impl ShareKit { pub fn share_file(&self, _url: String, _options: ShareFileOptions) -> crate::Result<()> { Err(crate::Error::UnsupportedPlatform) } + + pub fn get_pending_shared_content(&self) -> crate::Result> { + Err(crate::Error::UnsupportedPlatform) + } + + pub fn clear_pending_shared_content(&self) -> crate::Result<()> { + Err(crate::Error::UnsupportedPlatform) + } } diff --git a/src/lib.rs b/src/lib.rs index 6bc561d..8a217ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,9 @@ pub fn init() -> TauriPlugin { Builder::new("sharekit") .invoke_handler(tauri::generate_handler![ commands::share_text, - commands::share_file + commands::share_file, + commands::get_pending_shared_content, + commands::clear_pending_shared_content ]) .setup(|app, api| { #[cfg(mobile)] diff --git a/src/macos.rs b/src/macos.rs index 66268b8..933e721 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -144,4 +144,12 @@ impl ShareKit { Ok(()) } + + pub fn get_pending_shared_content(&self) -> crate::Result> { + Ok(None) + } + + pub fn clear_pending_shared_content(&self) -> crate::Result<()> { + Ok(()) + } } diff --git a/src/mobile.rs b/src/mobile.rs index eef0317..bc498d9 100644 --- a/src/mobile.rs +++ b/src/mobile.rs @@ -39,4 +39,16 @@ impl ShareKit { .run_mobile_plugin("shareFile", ShareFilePayload { url, options }) .map_err(Into::into) } + + pub fn get_pending_shared_content(&self) -> crate::Result> { + self.0 + .run_mobile_plugin("getPendingSharedContent", ()) + .map_err(Into::into) + } + + pub fn clear_pending_shared_content(&self) -> crate::Result<()> { + self.0 + .run_mobile_plugin("clearPendingSharedContent", ()) + .map_err(Into::into) + } } diff --git a/src/models.rs b/src/models.rs index 8ffd95e..64f305a 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,4 +1,4 @@ -use serde::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Serialize)] #[serde(rename_all = "camelCase")] @@ -29,3 +29,32 @@ pub struct ShareFilePayload { #[serde(flatten)] pub options: ShareFileOptions, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SharedContentType { + Text, + Files, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SharedFile { + pub path: String, + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SharedContent { + #[serde(rename = "type")] + pub content_type: SharedContentType, + #[serde(skip_serializing_if = "Option::is_none")] + pub text: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub files: Option>, +} diff --git a/src/windows.rs b/src/windows.rs index 19289e0..132cf47 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -235,4 +235,12 @@ impl ShareKit { Err(Error::WindowsApi("Share cancelled".to_string())) } } + + pub fn get_pending_shared_content(&self) -> crate::Result> { + Ok(None) + } + + pub fn clear_pending_shared_content(&self) -> crate::Result<()> { + Ok(()) + } } From 14c7b3827056663086c5ec45d57afd64f7eef677 Mon Sep 17 00:00:00 2001 From: Vladimir Pankratov Date: Tue, 30 Dec 2025 19:37:30 +0100 Subject: [PATCH 02/26] Fix image preview by enabling asset protocol correctly --- README.md | 36 +++++++++++++++++++ examples/share-demo/src-tauri/Cargo.toml | 2 +- examples/share-demo/src-tauri/tauri.conf.json | 6 +++- examples/share-demo/src/routes/+page.svelte | 16 +++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index caf5122..5ce5b74 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,42 @@ Add these intent filters inside your `` tag: You can customize the `mimeType` values to only accept specific file types. +### Displaying Received Images + +To display received images in your app, enable the asset protocol feature and configure the scope. + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri = { version = "2", features = ["protocol-asset"] } +``` + +`src-tauri/tauri.conf.json` + +```json +{ + "app": { + "security": { + "assetProtocol": { + "enable": true, + "scope": ["$CACHE/**", "$APPCACHE/**"] + } + } + } +} +``` + +Then in your frontend: + +```javascript +import { convertFileSrc } from "@tauri-apps/api/core"; + +// file.path is from SharedContent.files +const imageUrl = convertFileSrc(file.path); +// Use imageUrl in an tag +``` + ## Contributing PRs accepted. Please make sure to read the Contributing Guide before making a pull request. diff --git a/examples/share-demo/src-tauri/Cargo.toml b/examples/share-demo/src-tauri/Cargo.toml index 4a2f8b3..087b830 100644 --- a/examples/share-demo/src-tauri/Cargo.toml +++ b/examples/share-demo/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } +tauri = { version = "2", features = ["protocol-asset"] } tauri-plugin-opener = "2" tauri-plugin-sharekit = { path = "../../.." } serde = { version = "1", features = ["derive"] } diff --git a/examples/share-demo/src-tauri/tauri.conf.json b/examples/share-demo/src-tauri/tauri.conf.json index 12c78d0..4c845a2 100644 --- a/examples/share-demo/src-tauri/tauri.conf.json +++ b/examples/share-demo/src-tauri/tauri.conf.json @@ -18,7 +18,11 @@ } ], "security": { - "csp": null + "csp": null, + "assetProtocol": { + "enable": true, + "scope": ["$CACHE/**", "$APPCACHE/**"] + } } }, "bundle": { diff --git a/examples/share-demo/src/routes/+page.svelte b/examples/share-demo/src/routes/+page.svelte index 1556684..0939f81 100644 --- a/examples/share-demo/src/routes/+page.svelte +++ b/examples/share-demo/src/routes/+page.svelte @@ -1,5 +1,6 @@ @@ -149,6 +172,12 @@

{fileStatus}

{/if}
+ +
+

Log Output

+
{logs || 'No activity yet...'}
+ +