Skip to main content

tauri_plugin_vnidrop_share/
models.rs

1use serde::{Deserialize, Serialize};
2
3/// Represents a file to be shared, including its content, name, and MIME type.
4///
5/// The `data` field holds the Base64 encoded content of the file. This approach
6/// allows files to be easily passed from the frontend to the Rust backend
7/// without needing to manage local file paths directly.
8#[derive(Debug, Deserialize, Serialize, Clone)]
9#[serde(rename_all = "camelCase")]
10pub struct SharedFile {
11    pub data: String,
12    pub name: String,
13    pub mime_type: String,
14}
15
16/// Defines the content and options for a native sharing dialog.
17///
18/// This struct can be used to share text, a title, a URL, and a list of files.
19/// All fields are optional, allowing for flexible sharing payloads.
20///
21/// ## Examples
22///
23/// To share a simple message and URL:
24///
25/// ```json
26/// {
27///   "title": "My Tauri App",
28///   "text": "Check out this great app built with Tauri!",
29///   "url": "[https://tauri.app](https://tauri.app)"
30/// }
31/// ```
32///
33/// To share a file (e.g., an image in Base64 format):
34///
35/// ```json
36/// {
37///   "files": [
38///     {
39///       "data": "data:image/png;base64,iVBORw0KGgo...",
40///       "name": "my-image.png",
41///       "mimeType": "image/png"
42///     }
43///   ]
44/// }
45/// ```
46#[derive(Debug, Deserialize, Serialize, Clone)]
47#[serde(rename_all = "camelCase")]
48pub struct ShareOptions {
49    /// Optional text content to include in the share dialog.
50    pub text: Option<String>,
51    /// Optional title for the share dialog. (This is mainly used on Android)
52    pub title: Option<String>,
53    /// Optional URL to include in the share dialog.
54    pub url: Option<String>,
55    /// A list of files to share, each represented by a `SharedFile` struct.
56    pub files: Option<Vec<SharedFile>>,
57}
58
59impl ShareOptions {
60    /// Returns true when the payload contains at least one shareable value.
61    pub fn has_shareable_content(&self) -> bool {
62        self.text.as_ref().is_some_and(|value| !value.is_empty())
63            || self.url.as_ref().is_some_and(|value| !value.is_empty())
64            || self.files.as_ref().is_some_and(|files| !files.is_empty())
65    }
66
67    /// Combines text and URL for platforms that expose one plain-text field.
68    pub fn combined_text(&self) -> Option<String> {
69        match (self.text.as_deref(), self.url.as_deref()) {
70            (Some(text), Some(url)) if !text.is_empty() && !url.is_empty() => {
71                Some(format!("{text}\n{url}"))
72            }
73            (Some(text), _) if !text.is_empty() => Some(text.to_string()),
74            (_, Some(url)) if !url.is_empty() => Some(url.to_string()),
75            _ => None,
76        }
77    }
78}
79
80/// The result type for the `can_share` command.
81///
82/// A `true` value indicates that the current platform supports native sharing.
83/// The [`crate::commands::can_share`] command will return `true` on Windows, macOS, and mobile platforms,
84/// and `false` on Linux since there is no native sharing dialog available.
85#[derive(Debug, Deserialize, Serialize, Clone)]
86#[serde(rename_all = "camelCase")]
87pub struct CanShareResult {
88    pub value: bool,
89}
90
91#[cfg(test)]
92mod tests {
93    use super::{ShareOptions, SharedFile};
94
95    fn options(
96        text: Option<&str>,
97        url: Option<&str>,
98        files: Option<Vec<SharedFile>>,
99    ) -> ShareOptions {
100        ShareOptions {
101            text: text.map(ToString::to_string),
102            title: None,
103            url: url.map(ToString::to_string),
104            files,
105        }
106    }
107
108    #[test]
109    fn empty_options_are_not_shareable() {
110        assert!(!options(None, None, None).has_shareable_content());
111        assert!(!options(Some(""), Some(""), Some(Vec::new())).has_shareable_content());
112    }
113
114    #[test]
115    fn text_url_or_files_are_shareable() {
116        let file = SharedFile {
117            data: "aGVsbG8=".to_string(),
118            name: "hello.txt".to_string(),
119            mime_type: "text/plain".to_string(),
120        };
121
122        assert!(options(Some("hello"), None, None).has_shareable_content());
123        assert!(options(None, Some("https://example.com"), None).has_shareable_content());
124        assert!(options(None, None, Some(vec![file])).has_shareable_content());
125    }
126
127    #[test]
128    fn combined_text_preserves_text_and_url() {
129        let data = options(Some("hello"), Some("https://example.com"), None);
130        assert_eq!(
131            data.combined_text().as_deref(),
132            Some("hello\nhttps://example.com")
133        );
134    }
135}