tauri_plugin_android_fs/api/file_picker.rs
1use sync_async::sync_async;
2use crate::*;
3use super::*;
4
5
6/// API of file/dir picker.
7///
8/// # Examples
9/// ```no_run
10/// fn example(app: &tauri::AppHandle) {
11/// use tauri_plugin_android_fs::AndroidFsExt as _;
12///
13/// let api = app.android_fs();
14/// let file_picker = api.file_picker();
15/// }
16/// ```
17#[sync_async]
18pub struct FilePicker<'a, R: tauri::Runtime> {
19 #[cfg(target_os = "android")]
20 pub(crate) handle: &'a tauri::plugin::PluginHandle<R>,
21
22 #[cfg(not(target_os = "android"))]
23 #[allow(unused)]
24 pub(crate) handle: &'a std::marker::PhantomData<fn() -> R>,
25}
26
27#[cfg(target_os = "android")]
28#[sync_async(
29 use(if_sync) impls::SyncImpls as Impls;
30 use(if_async) impls::AsyncImpls as Impls;
31)]
32impl<'a, R: tauri::Runtime> FilePicker<'a, R> {
33
34 #[always_sync]
35 fn impls(&self) -> Impls<'_, R> {
36 Impls { handle: &self.handle }
37 }
38}
39
40#[sync_async(
41 use(if_async) api_async::{AndroidFs, FileOpener, PrivateStorage, PublicStorage};
42 use(if_sync) api_sync::{AndroidFs, FileOpener, PrivateStorage, PublicStorage};
43)]
44impl<'a, R: tauri::Runtime> FilePicker<'a, R> {
45
46 /// Opens a system file picker and returns a **read-write** URIs.
47 /// If no file is selected or the user cancels, an empty vec is returned.
48 ///
49 /// By default, returned URI is valid until the app or device is terminated.
50 /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
51 ///
52 /// This provides a standardized file explorer-style interface,
53 /// and also allows file selection from part of third-party apps or cloud storage.
54 ///
55 /// Removing the returned files is also supported in most cases,
56 /// but note that files provided by third-party apps may not be removable.
57 ///
58 /// # Args
59 /// - ***initial_location*** :
60 /// Indicate the initial location of dialog.
61 /// This URI works even without any permissions.
62 /// There is no need to use this if there is no special reason.
63 /// System will do its best to launch the dialog in the specified entry
64 /// if it's a directory, or the directory that contains the specified file if not.
65 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
66 /// This must be a URI taken from following or it's derivative :
67 /// - [`PublicStorage::resolve_initial_location`]
68 /// - [`AndroidFs::resolve_root_initial_location`]
69 /// - [`FilePicker::pick_files`]
70 /// - [`FilePicker::pick_file`]
71 /// - [`FilePicker::pick_dir`]
72 /// - [`FilePicker::save_file`]
73 ///
74 /// - ***mime_types*** :
75 /// The MIME types of the file to be selected.
76 /// However, there is no guarantee that the returned file will match the specified types.
77 /// If left empty, all file types will be available (equivalent to `["*/*"]`).
78 ///
79 /// # Support
80 /// All Android version.
81 ///
82 /// # References
83 /// - <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
84 #[maybe_async]
85 pub fn pick_files(
86 &self,
87 initial_location: Option<&FileUri>,
88 mime_types: &[&str],
89 ) -> Result<Vec<FileUri>> {
90
91 #[cfg(not(target_os = "android"))] {
92 Err(Error::NOT_ANDROID)
93 }
94 #[cfg(target_os = "android")] {
95 self.impls().show_pick_file_dialog(initial_location, mime_types, true).await
96 }
97 }
98
99 /// Opens a system file picker and returns a **read-write** URI.
100 /// If no file is selected or the user cancels, None is returned.
101 ///
102 /// By default, returned URI is valid until the app or device is terminated.
103 /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
104 ///
105 /// This provides a standardized file explorer-style interface,
106 /// and also allows file selection from part of third-party apps or cloud storage.
107 ///
108 /// Removing the returned files is also supported in most cases,
109 /// but note that files provided by third-party apps may not be removable.
110 ///
111 /// # Args
112 /// - ***initial_location*** :
113 /// Indicate the initial location of dialog.
114 /// This URI works even without any permissions.
115 /// There is no need to use this if there is no special reason.
116 /// System will do its best to launch the dialog in the specified entry
117 /// if it's a directory, or the directory that contains the specified file if not.
118 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
119 /// This must be a URI taken from following or it's derivative :
120 /// - [`PublicStorage::resolve_initial_location`]
121 /// - [`AndroidFs::resolve_root_initial_location`]
122 /// - [`FilePicker::pick_files`]
123 /// - [`FilePicker::pick_file`]
124 /// - [`FilePicker::pick_dir`]
125 /// - [`FilePicker::save_file`]
126 ///
127 /// - ***mime_types*** :
128 /// The MIME types of the file to be selected.
129 /// However, there is no guarantee that the returned file will match the specified types.
130 /// If left empty, all file types will be available (equivalent to `["*/*"]`).
131 ///
132 /// # Support
133 /// All Android version.
134 ///
135 /// # References
136 /// - <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
137 #[maybe_async]
138 pub fn pick_file(
139 &self,
140 initial_location: Option<&FileUri>,
141 mime_types: &[&str],
142 ) -> Result<Option<FileUri>> {
143
144 #[cfg(not(target_os = "android"))] {
145 Err(Error::NOT_ANDROID)
146 }
147 #[cfg(target_os = "android")] {
148 self.impls().show_pick_file_dialog(initial_location, mime_types, false)
149 .await
150 .map(|mut i| i.pop())
151 }
152 }
153
154 /// Opens a media picker and returns a **readonly** URIs.
155 /// If no file is selected or the user cancels, an empty vec is returned.
156 ///
157 /// By default, returned URI is valid until the app or device is terminated.
158 /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
159 ///
160 /// This media picker provides a gallery,
161 /// sorted by date from newest to oldest.
162 ///
163 /// # Args
164 /// - ***target*** :
165 /// The media type of the file to be selected.
166 /// Images or videos, or both.
167 ///
168 /// # Note
169 /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].
170 /// Instead, it will be assigned a sequential number, such as `1000091523.png`.
171 /// And this is marked intended behavior, not a bug.
172 /// - <https://issuetracker.google.com/issues/268079113>
173 ///
174 /// # Support
175 /// This feature is available on devices that meet the following criteria:
176 /// - Running Android 11 (API level 30) or higher
177 /// - Receive changes to Modular System Components through Google System Updates
178 ///
179 /// Availability on a given device can be verified by calling [`FilePicker::is_visual_media_picker_available`].
180 /// If not supported, this function behaves the same as [`FilePicker::pick_files`].
181 ///
182 /// # References
183 /// - <https://developer.android.com/training/data-storage/shared/photopicker>
184 #[maybe_async]
185 pub fn pick_visual_medias(
186 &self,
187 target: VisualMediaTarget<'_>,
188 ) -> Result<Vec<FileUri>> {
189
190 #[cfg(not(target_os = "android"))] {
191 Err(Error::NOT_ANDROID)
192 }
193 #[cfg(target_os = "android")] {
194 self.impls().show_pick_visual_media_dialog(target, true).await
195 }
196 }
197
198 /// Opens a media picker and returns a **readonly** URI.
199 /// If no file is selected or the user cancels, None is returned.
200 ///
201 /// By default, returned URI is valid until the app or device is terminated.
202 /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
203 ///
204 /// This media picker provides a gallery,
205 /// sorted by date from newest to oldest.
206 ///
207 /// # Args
208 /// - ***target*** :
209 /// The media type of the file to be selected.
210 /// Images or videos, or both.
211 ///
212 /// # Note
213 /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].
214 /// Instead, it will be assigned a sequential number, such as `1000091523.png`.
215 /// And this is marked intended behavior, not a bug.
216 /// - <https://issuetracker.google.com/issues/268079113>
217 ///
218 /// # Support
219 /// This feature is available on devices that meet the following criteria:
220 /// - Running Android 11 (API level 30) or higher
221 /// - Receive changes to Modular System Components through Google System Updates
222 ///
223 /// Availability on a given device can be verified by calling [`FilePicker::is_visual_media_picker_available`].
224 /// If not supported, this function behaves the same as [`FilePicker::pick_file`].
225 ///
226 /// # References
227 /// - <https://developer.android.com/training/data-storage/shared/photopicker>
228 #[maybe_async]
229 pub fn pick_visual_media(
230 &self,
231 target: VisualMediaTarget<'_>,
232 ) -> Result<Option<FileUri>> {
233
234 #[cfg(not(target_os = "android"))] {
235 Err(Error::NOT_ANDROID)
236 }
237 #[cfg(target_os = "android")] {
238 self.impls().show_pick_visual_media_dialog(target, false)
239 .await
240 .map(|mut i| i.pop())
241 }
242 }
243
244 /// Opens a file picker and returns a **readonly** URIs.
245 /// If no file is selected or the user cancels, an empty vec is returned.
246 ///
247 /// Returned URI is valid until the app or device is terminated. Can not persist it.
248 ///
249 /// This works differently depending on the model and version.
250 /// Recent devices often have the similar behaviour as [`FilePicker::pick_visual_medias`] or [`FilePicker::pick_files`].
251 /// In older versions, third-party apps often handle request instead.
252 ///
253 /// # Args
254 /// - ***mime_types*** :
255 /// The MIME types of the file to be selected.
256 /// However, there is no guarantee that the returned file will match the specified types.
257 /// If left empty, all file types will be available (equivalent to `["*/*"]`).
258 ///
259 /// # Support
260 /// All Android version.
261 ///
262 /// # References
263 /// - <https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT>
264 #[maybe_async]
265 pub fn pick_contents(
266 &self,
267 mime_types: &[&str],
268 ) -> Result<Vec<FileUri>> {
269
270 #[cfg(not(target_os = "android"))] {
271 Err(Error::NOT_ANDROID)
272 }
273 #[cfg(target_os = "android")] {
274 self.impls().show_pick_content_dialog(mime_types, true).await
275 }
276 }
277
278 /// Opens a file picker and returns a **readonly** URI.
279 /// If no file is selected or the user cancels, None is returned.
280 ///
281 /// Returned URI is valid until the app or device is terminated. Can not persist it.
282 ///
283 /// This works differently depending on the model and version.
284 /// Recent devices often have the similar behaviour as [`FilePicker::pick_visual_media`] or [`FilePicker::pick_file`].
285 /// In older versions, third-party apps often handle request instead.
286 ///
287 /// # Args
288 /// - ***mime_types*** :
289 /// The MIME types of the file to be selected.
290 /// However, there is no guarantee that the returned file will match the specified types.
291 /// If left empty, all file types will be available (equivalent to `["*/*"]`).
292 ///
293 /// # Support
294 /// All Android version.
295 ///
296 /// # References
297 /// - <https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT>
298 #[maybe_async]
299 pub fn pick_content(
300 &self,
301 mime_types: &[&str],
302 ) -> Result<Option<FileUri>> {
303
304 #[cfg(not(target_os = "android"))] {
305 Err(Error::NOT_ANDROID)
306 }
307 #[cfg(target_os = "android")] {
308 self.impls().show_pick_content_dialog(mime_types, false)
309 .await
310 .map(|mut i| i.pop())
311 }
312 }
313
314 /// Opens a system directory picker, allowing the creation of a new directory or the selection of an existing one,
315 /// and returns a **read-write** directory URI.
316 /// App can fully manage entries within the returned directory.
317 /// If no directory is selected or the user cancels, `None` is returned.
318 ///
319 /// By default, returned URI is valid until the app or device is terminated.
320 /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
321 ///
322 /// This provides a standardized file explorer-style interface,
323 /// and also allows directory selection from part of third-party apps or cloud storage.
324 ///
325 /// # Args
326 /// - ***initial_location*** :
327 /// Indicate the initial location of dialog.
328 /// This URI works even without any permissions.
329 /// There is no need to use this if there is no special reason.
330 /// System will do its best to launch the dialog in the specified entry
331 /// if it's a directory, or the directory that contains the specified file if not.
332 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
333 /// This must be a URI taken from following or it's derivative :
334 /// - [`PublicStorage::resolve_initial_location`]
335 /// - [`AndroidFs::resolve_root_initial_location`]
336 /// - [`FilePicker::pick_files`]
337 /// - [`FilePicker::pick_file`]
338 /// - [`FilePicker::pick_dir`]
339 /// - [`FilePicker::save_file`]
340 ///
341 /// # Support
342 /// All Android version.
343 ///
344 /// # References
345 /// - <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT_TREE>
346 #[maybe_async]
347 pub fn pick_dir(
348 &self,
349 initial_location: Option<&FileUri>,
350 ) -> Result<Option<FileUri>> {
351
352 #[cfg(not(target_os = "android"))] {
353 Err(Error::NOT_ANDROID)
354 }
355 #[cfg(target_os = "android")] {
356 self.impls().show_pick_dir_dialog(initial_location).await
357 }
358 }
359
360 /// Opens a system file saver and returns a **writeonly** URI.
361 /// The returned file may be a newly created file with no content,
362 /// or it may be an existing file with the requested MIME type.
363 /// If the user cancels, `None` is returned.
364 ///
365 /// By default, returned URI is valid until the app or device is terminated.
366 /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
367 ///
368 /// This provides a standardized file explorer-style interface,
369 /// and also allows file selection from part of third-party apps or cloud storage.
370 ///
371 /// Removing and reading the returned files is also supported in most cases,
372 /// but note that files provided by third-party apps may not.
373 ///
374 /// # Args
375 /// - ***initial_location*** :
376 /// Indicate the initial location of dialog.
377 /// This URI works even without any permissions.
378 /// There is no need to use this if there is no special reason.
379 /// System will do its best to launch the dialog in the specified entry
380 /// if it's a directory, or the directory that contains the specified file if not.
381 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
382 /// This must be a URI taken from following or it's derivative :
383 /// - [`PublicStorage::resolve_initial_location`]
384 /// - [`AndroidFs::resolve_root_initial_location`]
385 /// - [`FilePicker::pick_files`]
386 /// - [`FilePicker::pick_file`]
387 /// - [`FilePicker::pick_dir`]
388 /// - [`FilePicker::save_file`]
389 ///
390 /// - ***initial_file_name*** :
391 /// An initial file name.
392 /// The user may change this value before creating the file.
393 /// If no extension is present,
394 /// the system may infer one from ***mime_type*** and may append it to the file name.
395 /// But this append-extension operation depends on the model and version.
396 ///
397 /// - ***mime_type*** :
398 /// The MIME type of the file to be saved.
399 /// If this is None, MIME type is inferred from the extension of ***initial_file_name*** (not file name by user input)
400 /// and if that fails, `application/octet-stream` is used.
401 ///
402 /// # Support
403 /// All Android version.
404 ///
405 /// # References
406 /// - <https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT>
407 #[maybe_async]
408 pub fn save_file(
409 &self,
410 initial_location: Option<&FileUri>,
411 initial_file_name: impl AsRef<str>,
412 mime_type: Option<&str>,
413 ) -> Result<Option<FileUri>> {
414
415 #[cfg(not(target_os = "android"))] {
416 Err(Error::NOT_ANDROID)
417 }
418 #[cfg(target_os = "android")] {
419 self.impls().show_save_file_dialog(initial_location, initial_file_name, mime_type).await
420 }
421 }
422
423 /// Verify whether [`FilePicker::pick_visual_medias`] is available on a given device.
424 ///
425 /// # Support
426 /// All Android version.
427 #[maybe_async]
428 pub fn is_visual_media_picker_available(&self) -> Result<bool> {
429 #[cfg(not(target_os = "android"))] {
430 Err(Error::NOT_ANDROID)
431 }
432 #[cfg(target_os = "android")] {
433 self.impls().is_visual_media_picker_available().await
434 }
435 }
436}