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