Skip to content

Commit 4dfbcb7

Browse files
committed
Implemented android save dialog.
1 parent b1e5cae commit 4dfbcb7

File tree

4 files changed

+77
-5
lines changed

4 files changed

+77
-5
lines changed

plugins/dialog/android/src/main/java/DialogPlugin.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class MessageOptions {
4141
var cancelButtonLabel: String? = null
4242
}
4343

44+
@InvokeArg
45+
class SaveFileDialogOptions {
46+
var title: String = ""
47+
}
48+
4449
@TauriPlugin
4550
class DialogPlugin(private val activity: Activity): Plugin(activity) {
4651
var filePickerOptions: FilePickerOptions? = null
@@ -204,4 +209,46 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
204209
dialog.show()
205210
}
206211
}
212+
213+
@Command
214+
fun saveFileDialog(invoke: Invoke) {
215+
try {
216+
val args = invoke.parseArgs(SaveFileDialogOptions::class.java)
217+
218+
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
219+
intent.addCategory(Intent.CATEGORY_OPENABLE)
220+
intent.setType("text/plain")
221+
intent.putExtra(Intent.EXTRA_TITLE, args.title)
222+
startActivityForResult(invoke, intent, "saveFileDialogResult")
223+
} catch (ex: Exception) {
224+
val message = ex.message ?: "Failed to pick save file"
225+
Logger.error(message)
226+
invoke.reject(message)
227+
}
228+
}
229+
230+
@ActivityCallback
231+
fun saveFileDialogResult(invoke: Invoke, result: ActivityResult) {
232+
try {
233+
when (result.resultCode) {
234+
Activity.RESULT_OK -> {
235+
val callResult = JSObject()
236+
val intent: Intent? = result.data
237+
if (intent != null) {
238+
val uri = intent.getData()
239+
if (uri != null) {
240+
callResult.put("file", uri.toString())
241+
}
242+
}
243+
invoke.resolve(callResult)
244+
}
245+
Activity.RESULT_CANCELED -> invoke.reject("File picker cancelled")
246+
else -> invoke.reject("Failed to pick files")
247+
}
248+
} catch (ex: java.lang.Exception) {
249+
val message = ex.message ?: "Failed to read file pick result"
250+
Logger.error(message)
251+
invoke.reject(message)
252+
}
253+
}
207254
}

plugins/dialog/src/commands.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,9 @@ pub(crate) async fn save<R: Runtime>(
193193
dialog: State<'_, Dialog<R>>,
194194
options: SaveDialogOptions,
195195
) -> Result<Option<PathBuf>> {
196-
#[cfg(mobile)]
196+
#[cfg(any(target_os = "ios"))]
197197
return Err(crate::Error::FileSaveDialogNotImplemented);
198-
#[cfg(desktop)]
198+
#[cfg(any(desktop, target_os = "android"))]
199199
{
200200
let mut dialog_builder = dialog.file();
201201
#[cfg(any(windows, target_os = "macos"))]

plugins/dialog/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ use tauri::{
1717
Manager, Runtime,
1818
};
1919

20+
#[cfg(any(desktop, target_os = "ios"))]
21+
use std::fs;
22+
2023
use std::{
21-
fs,
2224
path::{Path, PathBuf},
2325
sync::mpsc::sync_channel,
2426
};
@@ -471,7 +473,6 @@ impl<R: Runtime> FileDialogBuilder<R> {
471473
/// })
472474
/// })
473475
/// ```
474-
#[cfg(desktop)]
475476
pub fn save_file<F: FnOnce(Option<PathBuf>) + Send + 'static>(self, f: F) {
476477
save_file(self, f)
477478
}
@@ -572,14 +573,15 @@ impl<R: Runtime> FileDialogBuilder<R> {
572573
/// // the file path is `None` if the user closed the dialog
573574
/// }
574575
/// ```
575-
#[cfg(desktop)]
576576
pub fn blocking_save_file(self) -> Option<PathBuf> {
577577
blocking_fn!(self, save_file)
578578
}
579+
579580
}
580581

581582
// taken from deno source code: https://github.com/denoland/deno/blob/ffffa2f7c44bd26aec5ae1957e0534487d099f48/runtime/ops/fs.rs#L913
582583
#[inline]
584+
#[allow(unused)]
583585
fn to_msec(maybe_time: std::result::Result<std::time::SystemTime, std::io::Error>) -> Option<u64> {
584586
match maybe_time {
585587
Ok(time) => {

plugins/dialog/src/mobile.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
4+
use std::path::PathBuf;
45

56
use serde::{de::DeserializeOwned, Deserialize};
67
use tauri::{
@@ -49,6 +50,11 @@ struct FilePickerResponse {
4950
files: Vec<FileResponse>,
5051
}
5152

53+
#[derive(Debug, Deserialize)]
54+
struct SaveFileResponse {
55+
file: PathBuf,
56+
}
57+
5258
pub fn pick_file<R: Runtime, F: FnOnce(Option<FileResponse>) + Send + 'static>(
5359
dialog: FileDialogBuilder<R>,
5460
f: F,
@@ -83,6 +89,23 @@ pub fn pick_files<R: Runtime, F: FnOnce(Option<Vec<FileResponse>>) + Send + 'sta
8389
});
8490
}
8591

92+
pub fn save_file<R: Runtime, F: FnOnce(Option<PathBuf>) + Send + 'static>(
93+
dialog: FileDialogBuilder<R>,
94+
f: F,
95+
) {
96+
std::thread::spawn(move || {
97+
let res = dialog
98+
.dialog
99+
.0
100+
.run_mobile_plugin::<SaveFileResponse>("saveFileDialog", dialog.payload(true));
101+
if let Ok(response) = res {
102+
f(Some(response.file))
103+
} else {
104+
f(None)
105+
}
106+
});
107+
}
108+
86109
#[derive(Debug, Deserialize)]
87110
struct ShowMessageDialogResponse {
88111
#[allow(dead_code)]

0 commit comments

Comments
 (0)