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