From 000dbfb491fa45302d68c3400c1d35e9267ad154 Mon Sep 17 00:00:00 2001 From: mikoto2000 Date: Fri, 26 Jul 2024 10:59:12 +0000 Subject: [PATCH 1/3] Implemented android save dialog. --- .../android/src/main/java/DialogPlugin.kt | 47 +++++++++++++++++++ plugins/dialog/src/commands.rs | 4 +- plugins/dialog/src/lib.rs | 8 ++-- plugins/dialog/src/mobile.rs | 23 +++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/plugins/dialog/android/src/main/java/DialogPlugin.kt b/plugins/dialog/android/src/main/java/DialogPlugin.kt index 73eacfb66c..6cc15179eb 100644 --- a/plugins/dialog/android/src/main/java/DialogPlugin.kt +++ b/plugins/dialog/android/src/main/java/DialogPlugin.kt @@ -41,6 +41,11 @@ class MessageOptions { var cancelButtonLabel: String? = null } +@InvokeArg +class SaveFileDialogOptions { + var title: String = "" +} + @TauriPlugin class DialogPlugin(private val activity: Activity): Plugin(activity) { var filePickerOptions: FilePickerOptions? = null @@ -204,4 +209,46 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) { dialog.show() } } + + @Command + fun saveFileDialog(invoke: Invoke) { + try { + val args = invoke.parseArgs(SaveFileDialogOptions::class.java) + + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.setType("text/plain") + intent.putExtra(Intent.EXTRA_TITLE, args.title) + startActivityForResult(invoke, intent, "saveFileDialogResult") + } catch (ex: Exception) { + val message = ex.message ?: "Failed to pick save file" + Logger.error(message) + invoke.reject(message) + } + } + + @ActivityCallback + fun saveFileDialogResult(invoke: Invoke, result: ActivityResult) { + try { + when (result.resultCode) { + Activity.RESULT_OK -> { + val callResult = JSObject() + val intent: Intent? = result.data + if (intent != null) { + val uri = intent.getData() + if (uri != null) { + callResult.put("file", uri.toString()) + } + } + invoke.resolve(callResult) + } + Activity.RESULT_CANCELED -> invoke.reject("File picker cancelled") + else -> invoke.reject("Failed to pick files") + } + } catch (ex: java.lang.Exception) { + val message = ex.message ?: "Failed to read file pick result" + Logger.error(message) + invoke.reject(message) + } + } } \ No newline at end of file diff --git a/plugins/dialog/src/commands.rs b/plugins/dialog/src/commands.rs index 0d4a0de9ac..0fb40cc2e7 100644 --- a/plugins/dialog/src/commands.rs +++ b/plugins/dialog/src/commands.rs @@ -193,9 +193,9 @@ pub(crate) async fn save( dialog: State<'_, Dialog>, options: SaveDialogOptions, ) -> Result> { - #[cfg(mobile)] + #[cfg(any(target_os = "ios"))] return Err(crate::Error::FileSaveDialogNotImplemented); - #[cfg(desktop)] + #[cfg(any(desktop, target_os = "android"))] { let mut dialog_builder = dialog.file(); #[cfg(any(windows, target_os = "macos"))] diff --git a/plugins/dialog/src/lib.rs b/plugins/dialog/src/lib.rs index bb1b9882c1..feac2ead41 100644 --- a/plugins/dialog/src/lib.rs +++ b/plugins/dialog/src/lib.rs @@ -17,8 +17,10 @@ use tauri::{ Manager, Runtime, }; +#[cfg(any(desktop, target_os = "ios"))] +use std::fs; + use std::{ - fs, path::{Path, PathBuf}, sync::mpsc::sync_channel, }; @@ -471,7 +473,6 @@ impl FileDialogBuilder { /// }) /// }) /// ``` - #[cfg(desktop)] pub fn save_file) + Send + 'static>(self, f: F) { save_file(self, f) } @@ -572,14 +573,15 @@ impl FileDialogBuilder { /// // the file path is `None` if the user closed the dialog /// } /// ``` - #[cfg(desktop)] pub fn blocking_save_file(self) -> Option { blocking_fn!(self, save_file) } + } // taken from deno source code: https://github.com/denoland/deno/blob/ffffa2f7c44bd26aec5ae1957e0534487d099f48/runtime/ops/fs.rs#L913 #[inline] +#[allow(unused)] fn to_msec(maybe_time: std::result::Result) -> Option { match maybe_time { Ok(time) => { diff --git a/plugins/dialog/src/mobile.rs b/plugins/dialog/src/mobile.rs index 289cbb7e3b..6672dc5e4b 100644 --- a/plugins/dialog/src/mobile.rs +++ b/plugins/dialog/src/mobile.rs @@ -1,6 +1,7 @@ // Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use std::path::PathBuf; use serde::{de::DeserializeOwned, Deserialize}; use tauri::{ @@ -49,6 +50,11 @@ struct FilePickerResponse { files: Vec, } +#[derive(Debug, Deserialize)] +struct SaveFileResponse { + file: PathBuf, +} + pub fn pick_file) + Send + 'static>( dialog: FileDialogBuilder, f: F, @@ -83,6 +89,23 @@ pub fn pick_files>) + Send + 'sta }); } +pub fn save_file) + Send + 'static>( + dialog: FileDialogBuilder, + f: F, +) { + std::thread::spawn(move || { + let res = dialog + .dialog + .0 + .run_mobile_plugin::("saveFileDialog", dialog.payload(true)); + if let Ok(response) = res { + f(Some(response.file)) + } else { + f(None) + } + }); +} + #[derive(Debug, Deserialize)] struct ShowMessageDialogResponse { #[allow(dead_code)] From daeb6b44e9e14fa2139ac4551c6575a6b8b98f5f Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 13 Aug 2024 08:21:17 -0300 Subject: [PATCH 2/3] small cleanup --- .changes/android-dialog-save.md | 5 +++++ .../api/src-tauri/gen/android/.idea/gradle.xml | 3 ++- .../api/src-tauri/gen/android/.idea/misc.xml | 1 - .../dialog/android/src/main/java/DialogPlugin.kt | 4 ++-- plugins/dialog/guest-js/index.ts | 16 +++++++++++++--- plugins/dialog/src/commands.rs | 14 +++++++++++++- plugins/dialog/src/error.rs | 4 ++-- plugins/dialog/src/lib.rs | 5 +++-- plugins/dialog/src/mobile.rs | 2 +- 9 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 .changes/android-dialog-save.md diff --git a/.changes/android-dialog-save.md b/.changes/android-dialog-save.md new file mode 100644 index 0000000000..9b4b2c4a17 --- /dev/null +++ b/.changes/android-dialog-save.md @@ -0,0 +1,5 @@ +--- +"dialog": patch:feat +--- + +Implement `save` API on Android. diff --git a/examples/api/src-tauri/gen/android/.idea/gradle.xml b/examples/api/src-tauri/gen/android/.idea/gradle.xml index 788c446585..83b872db27 100644 --- a/examples/api/src-tauri/gen/android/.idea/gradle.xml +++ b/examples/api/src-tauri/gen/android/.idea/gradle.xml @@ -1,5 +1,6 @@ +