From 778c99aa0c1e6ecc26aeed173788e5997f8a517a Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 13 Apr 2023 09:25:12 -0300 Subject: [PATCH] feat: add android file picker --- Cargo.lock | 18 +- .../android/src/main/java/DialogPlugin.kt | 133 ++++++++++++++ .../android/src/main/java/FilePickerUtils.kt | 165 ++++++++++++++++++ .../dialog/examples/tauri-app/package.json | 3 +- .../examples/tauri-app/src-tauri/Cargo.lock | 18 +- .../src-tauri/gen/android/app/.gitignore | 2 - .../java/app/tauri/app/kotlin/BuildTask.kt | 58 ++++++ .../java/app/tauri/app/kotlin/RustPlugin.kt | 59 +++++++ .../dialog/examples/tauri-app/src/App.svelte | 10 ++ .../tauri-app/src/lib/FileDialogs.svelte | 27 +++ plugins/dialog/examples/tauri-app/yarn.lock | 118 ++++++------- plugins/dialog/guest-js/index.ts | 26 ++- plugins/dialog/src/commands.rs | 93 +++++----- plugins/dialog/src/error.rs | 6 + plugins/dialog/src/lib.rs | 66 +++++-- plugins/dialog/src/mobile.rs | 72 +++----- pnpm-lock.yaml | 3 - 17 files changed, 696 insertions(+), 181 deletions(-) create mode 100644 plugins/dialog/android/src/main/java/FilePickerUtils.kt create mode 100644 plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/buildSrc/src/main/java/app/tauri/app/kotlin/BuildTask.kt create mode 100644 plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/buildSrc/src/main/java/app/tauri/app/kotlin/RustPlugin.kt create mode 100644 plugins/dialog/examples/tauri-app/src/lib/FileDialogs.svelte diff --git a/Cargo.lock b/Cargo.lock index 6590e85f..cbedd5fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4344,7 +4344,7 @@ dependencies = [ [[package]] name = "tauri" -version = "2.0.0-alpha.6" +version = "2.0.0-alpha.7" dependencies = [ "anyhow", "bytes 1.4.0", @@ -4394,7 +4394,7 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.4" dependencies = [ "anyhow", "cargo_toml", @@ -4412,7 +4412,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.4" dependencies = [ "base64 0.21.0", "brotli", @@ -4436,7 +4436,7 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.4" dependencies = [ "heck", "proc-macro2", @@ -4671,7 +4671,7 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.13.0-alpha.3" +version = "0.13.0-alpha.4" dependencies = [ "gtk", "http", @@ -4691,7 +4691,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "0.13.0-alpha.3" +version = "0.13.0-alpha.4" dependencies = [ "cocoa", "gtk", @@ -4710,7 +4710,7 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.4" dependencies = [ "brotli", "ctor", @@ -5813,9 +5813,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a04fc11ebe79d2ed88670a72f0bee21e35773b893993bfa577bd448af8ff2e5" +checksum = "e8cf0dbfa7ccbd2e3832a3098b19d4b552360ea00a40b244a99caef46bffd84f" dependencies = [ "base64 0.13.1", "block", diff --git a/plugins/dialog/android/src/main/java/DialogPlugin.kt b/plugins/dialog/android/src/main/java/DialogPlugin.kt index 5739ace3..8806de6d 100644 --- a/plugins/dialog/android/src/main/java/DialogPlugin.kt +++ b/plugins/dialog/android/src/main/java/DialogPlugin.kt @@ -6,16 +6,149 @@ package app.tauri.dialog import android.app.Activity import android.app.AlertDialog +import android.content.Intent +import android.net.Uri import android.os.Handler import android.os.Looper +import androidx.activity.result.ActivityResult +import app.tauri.Logger +import app.tauri.annotation.ActivityCallback import app.tauri.annotation.Command import app.tauri.annotation.TauriPlugin import app.tauri.plugin.Invoke +import app.tauri.plugin.JSArray import app.tauri.plugin.JSObject import app.tauri.plugin.Plugin +import org.json.JSONException + @TauriPlugin class DialogPlugin(private val activity: Activity): Plugin(activity) { + @Command + fun showFilePicker(invoke: Invoke) { + try { + val filters = invoke.getArray("filters", JSArray()) + val multiple = invoke.getBoolean("multiple", false) + val parsedTypes = parseFiltersOption(filters) + + val intent = if (parsedTypes != null && parsedTypes.isNotEmpty()) { + val intent = Intent(Intent.ACTION_PICK) + intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes) + + var uniqueMimeType = true + var mimeKind: String? = null + for (mime in parsedTypes) { + val kind = mime.split("/")[0] + if (mimeKind == null) { + mimeKind = kind + } else if (mimeKind != kind) { + uniqueMimeType = false + } + } + + intent.type = if (uniqueMimeType) Intent.normalizeMimeType("$mimeKind/*") else "*/*" + intent + } else { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "*/*" + intent + } + + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple) + + startActivityForResult(invoke, intent, "filePickerResult") + } catch (ex: Exception) { + val message = ex.message ?: "Failed to pick file" + Logger.error(message) + invoke.reject(message) + } + } + + @ActivityCallback + fun filePickerResult(invoke: Invoke, result: ActivityResult) { + try { + val readData = invoke.getBoolean("readData", false) + when (result.resultCode) { + Activity.RESULT_OK -> { + val callResult = createPickFilesResult(result.data, readData) + 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) + } + } + + private fun createPickFilesResult(data: Intent?, readData: Boolean): JSObject { + val callResult = JSObject() + val filesResultList: MutableList = ArrayList() + if (data == null) { + callResult.put("files", JSArray.from(filesResultList)) + return callResult + } + val uris: MutableList = ArrayList() + if (data.clipData == null) { + val uri: Uri? = data.data + uris.add(uri) + } else { + for (i in 0 until data.clipData!!.itemCount) { + val uri: Uri = data.clipData!!.getItemAt(i).uri + uris.add(uri) + } + } + for (i in uris.indices) { + val uri = uris[i] ?: continue + val fileResult = JSObject() + if (readData) { + fileResult.put("base64Data", FilePickerUtils.getDataFromUri(activity, uri)) + } + val duration = FilePickerUtils.getDurationFromUri(activity, uri) + if (duration != null) { + fileResult.put("duration", duration) + } + val resolution = FilePickerUtils.getHeightAndWidthFromUri(activity, uri) + if (resolution != null) { + fileResult.put("height", resolution.height) + fileResult.put("width", resolution.width) + } + fileResult.put("mimeType", FilePickerUtils.getMimeTypeFromUri(activity, uri)) + val modifiedAt = FilePickerUtils.getModifiedAtFromUri(activity, uri) + if (modifiedAt != null) { + fileResult.put("modifiedAt", modifiedAt) + } + fileResult.put("name", FilePickerUtils.getNameFromUri(activity, uri)) + fileResult.put("path", FilePickerUtils.getPathFromUri(uri)) + fileResult.put("size", FilePickerUtils.getSizeFromUri(activity, uri)) + filesResultList.add(fileResult) + } + callResult.put("files", JSArray.from(filesResultList.toTypedArray())) + return callResult + } + + private fun parseFiltersOption(filters: JSArray): Array? { + return try { + val filtersList: List = filters.toList() + val mimeTypes = mutableListOf() + for (filter in filtersList) { + val extensionsList = filter.getJSONArray("extensions") + for (i in 0 until extensionsList.length()) { + val mime = extensionsList.getString(i) + mimeTypes.add(if (mime == "text/csv") "text/comma-separated-values" else mime) + } + } + + mimeTypes.toTypedArray() + } catch (exception: JSONException) { + Logger.error("parseTypesOption failed.", exception) + null + } + } + @Command fun showMessageDialog(invoke: Invoke) { val title = invoke.getString("title") diff --git a/plugins/dialog/android/src/main/java/FilePickerUtils.kt b/plugins/dialog/android/src/main/java/FilePickerUtils.kt new file mode 100644 index 00000000..5f0854a1 --- /dev/null +++ b/plugins/dialog/android/src/main/java/FilePickerUtils.kt @@ -0,0 +1,165 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.dialog + +import android.content.Context +import android.graphics.BitmapFactory +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.provider.DocumentsContract +import android.provider.OpenableColumns +import android.util.Base64 +import app.tauri.Logger +import java.io.ByteArrayOutputStream +import java.io.FileNotFoundException +import java.io.IOException +import java.io.InputStream + +class FilePickerUtils { + class FileResolution(var height: Int, var width: Int) + + companion object { + fun getPathFromUri(uri: Uri): String { + return uri.toString() + } + + fun getNameFromUri(context: Context, uri: Uri): String? { + var displayName: String? = "" + val projection = arrayOf(OpenableColumns.DISPLAY_NAME) + val cursor = + context.contentResolver.query(uri, projection, null, null, null) + if (cursor != null) { + cursor.moveToFirst() + val columnIdx = cursor.getColumnIndex(projection[0]) + displayName = cursor.getString(columnIdx) + cursor.close() + } + if (displayName == null || displayName.isEmpty()) { + displayName = uri.lastPathSegment + } + return displayName + } + + fun getDataFromUri(context: Context, uri: Uri): String { + try { + val stream = context.contentResolver.openInputStream(uri) ?: return "" + val bytes = getBytesFromInputStream(stream) + return Base64.encodeToString(bytes, Base64.NO_WRAP) + } catch (e: FileNotFoundException) { + Logger.error("openInputStream failed.", e) + } catch (e: IOException) { + Logger.error("getBytesFromInputStream failed.", e) + } + return "" + } + + fun getMimeTypeFromUri(context: Context, uri: Uri): String? { + return context.contentResolver.getType(uri) + } + + fun getModifiedAtFromUri(context: Context, uri: Uri): Long? { + return try { + var modifiedAt: Long = 0 + val cursor = + context.contentResolver.query(uri, null, null, null, null) + if (cursor != null) { + cursor.moveToFirst() + val columnIdx = + cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED) + modifiedAt = cursor.getLong(columnIdx) + cursor.close() + } + modifiedAt + } catch (e: Exception) { + Logger.error("getModifiedAtFromUri failed.", e) + null + } + } + + fun getSizeFromUri(context: Context, uri: Uri): Long { + var size: Long = 0 + val projection = arrayOf(OpenableColumns.SIZE) + val cursor = + context.contentResolver.query(uri, projection, null, null, null) + if (cursor != null) { + cursor.moveToFirst() + val columnIdx = cursor.getColumnIndex(projection[0]) + size = cursor.getLong(columnIdx) + cursor.close() + } + return size + } + + fun getDurationFromUri(context: Context, uri: Uri): Long? { + if (isVideoUri(context, uri)) { + val retriever = MediaMetadataRetriever() + retriever.setDataSource(context, uri) + val time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) + val durationMs = time?.toLong() ?: 0 + try { + retriever.release() + } catch (e: Exception) { + Logger.error("MediaMetadataRetriever.release() failed.", e) + } + return durationMs / 1000L + } + return null + } + + fun getHeightAndWidthFromUri(context: Context, uri: Uri): FileResolution? { + if (isImageUri(context, uri)) { + val options = BitmapFactory.Options() + options.inJustDecodeBounds = true + return try { + BitmapFactory.decodeStream( + context.contentResolver.openInputStream(uri), + null, + options + ) + FileResolution(options.outHeight, options.outWidth) + } catch (exception: FileNotFoundException) { + exception.printStackTrace() + null + } + } else if (isVideoUri(context, uri)) { + val retriever = MediaMetadataRetriever() + retriever.setDataSource(context, uri) + val width = + Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) ?: "0") + val height = + Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) ?: "0") + try { + retriever.release() + } catch (e: Exception) { + Logger.error("MediaMetadataRetriever.release() failed.", e) + } + return FileResolution(height, width) + } + return null + } + + private fun isImageUri(context: Context, uri: Uri): Boolean { + val mimeType = getMimeTypeFromUri(context, uri) ?: return false + return mimeType.startsWith("image") + } + + private fun isVideoUri(context: Context, uri: Uri): Boolean { + val mimeType = getMimeTypeFromUri(context, uri) ?: return false + return mimeType.startsWith("video") + } + + @Throws(IOException::class) + private fun getBytesFromInputStream(`is`: InputStream): ByteArray { + val os = ByteArrayOutputStream() + val buffer = ByteArray(0xFFFF) + var len = `is`.read(buffer) + while (len != -1) { + os.write(buffer, 0, len) + len = `is`.read(buffer) + } + return os.toByteArray() + } + } +} \ No newline at end of file diff --git a/plugins/dialog/examples/tauri-app/package.json b/plugins/dialog/examples/tauri-app/package.json index eb7a6115..2236136a 100644 --- a/plugins/dialog/examples/tauri-app/package.json +++ b/plugins/dialog/examples/tauri-app/package.json @@ -10,12 +10,11 @@ "tauri": "tauri" }, "dependencies": { - "@tauri-apps/api": "^1.1.0", "tauri-plugin-dialog-api": "link:../../" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^1.0.1", - "@tauri-apps/cli": "^2.0.0-alpha.6", + "@tauri-apps/cli": "^2.0.0-alpha.7", "internal-ip": "^7.0.0", "svelte": "^3.49.0", "vite": "^3.0.2" diff --git a/plugins/dialog/examples/tauri-app/src-tauri/Cargo.lock b/plugins/dialog/examples/tauri-app/src-tauri/Cargo.lock index 65a5cd7c..0a8c32f5 100644 --- a/plugins/dialog/examples/tauri-app/src-tauri/Cargo.lock +++ b/plugins/dialog/examples/tauri-app/src-tauri/Cargo.lock @@ -3061,7 +3061,7 @@ dependencies = [ [[package]] name = "tauri" -version = "2.0.0-alpha.6" +version = "2.0.0-alpha.7" dependencies = [ "anyhow", "bytes", @@ -3118,7 +3118,7 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.4" dependencies = [ "anyhow", "cargo_toml", @@ -3136,7 +3136,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.4" dependencies = [ "base64 0.21.0", "brotli", @@ -3161,7 +3161,7 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.4" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -3205,7 +3205,7 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.13.0-alpha.3" +version = "0.13.0-alpha.4" dependencies = [ "gtk", "http", @@ -3225,7 +3225,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "0.13.0-alpha.3" +version = "0.13.0-alpha.4" dependencies = [ "cocoa", "gtk", @@ -3244,7 +3244,7 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.4" dependencies = [ "brotli", "ctor", @@ -4057,9 +4057,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.27.1" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b958a84f347bf8ec2e882b3f767bdb3f7797e89867bb9d6f0d1fe3df26754fe9" +checksum = "e8cf0dbfa7ccbd2e3832a3098b19d4b552360ea00a40b244a99caef46bffd84f" dependencies = [ "base64 0.13.1", "block", diff --git a/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/.gitignore b/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/.gitignore index 943a67a4..6bb2f5ee 100644 --- a/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/.gitignore +++ b/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/.gitignore @@ -9,8 +9,6 @@ /.idea/assetWizardSettings.xml .DS_Store build -/buildSrc/src/main/java/app/tauri/app/kotlin/BuildTask.kt -/buildSrc/src/main/java/app/tauri/app/kotlin/RustPlugin.kt /captures .externalNativeBuild .cxx diff --git a/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/buildSrc/src/main/java/app/tauri/app/kotlin/BuildTask.kt b/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/buildSrc/src/main/java/app/tauri/app/kotlin/BuildTask.kt new file mode 100644 index 00000000..2e777156 --- /dev/null +++ b/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/buildSrc/src/main/java/app/tauri/app/kotlin/BuildTask.kt @@ -0,0 +1,58 @@ +package app.tauri + +import java.io.File +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction + +open class BuildTask : DefaultTask() { + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + var rootDirRel: File? = null + @Input + var target: String? = null + @Input + var release: Boolean? = null + + @TaskAction + fun build() { + val executable = """yarn"""; + try { + runTauriCli(executable) + } catch (e: Exception){ + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + runTauriCli("$executable.cmd") + } else { + throw e; + } + } + } + + fun runTauriCli(executable: String) { + val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") + val target = target ?: throw GradleException("target cannot be null") + val release = release ?: throw GradleException("release cannot be null") + val args = listOf("tauri", "android", "android-studio-script"); + + project.exec { + workingDir(File(project.projectDir, rootDirRel.path)) + executable(executable) + args(args) + if (project.logger.isEnabled(LogLevel.DEBUG)) { + args("-vv") + } else if (project.logger.isEnabled(LogLevel.INFO)) { + args("-v") + } + if (release) { + args("--release") + } + args(listOf("--target", target)) + }.assertNormalExitValue() + } +} \ No newline at end of file diff --git a/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/buildSrc/src/main/java/app/tauri/app/kotlin/RustPlugin.kt b/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/buildSrc/src/main/java/app/tauri/app/kotlin/RustPlugin.kt new file mode 100644 index 00000000..f40ad054 --- /dev/null +++ b/plugins/dialog/examples/tauri-app/src-tauri/gen/android/app/buildSrc/src/main/java/app/tauri/app/kotlin/RustPlugin.kt @@ -0,0 +1,59 @@ +package app.tauri + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import java.io.File +import java.util.* + +const val TASK_GROUP = "rust" + +open class Config { + var rootDirRel: String? = null + var targets: List? = null + var arches: List? = null +} + +open class RustPlugin : Plugin { + private lateinit var config: Config + + override fun apply(project: Project) { + config = project.extensions.create("rust", Config::class.java) + project.afterEvaluate { + if (config.targets == null) { + throw GradleException("targets cannot be null") + } + if (config.arches == null) { + throw GradleException("arches cannot be null") + } + for (profile in listOf("debug", "release")) { + val profileCapitalized = profile.capitalize(Locale.ROOT) + val buildTask = project.tasks.maybeCreate( + "rustBuild$profileCapitalized", + DefaultTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for all targets" + } + for (targetPair in config.targets!!.withIndex()) { + val targetName = targetPair.value + val targetArch = config.arches!![targetPair.index] + val targetArchCapitalized = targetArch.capitalize(Locale.ROOT) + val targetBuildTask = project.tasks.maybeCreate( + "rustBuild$targetArchCapitalized$profileCapitalized", + BuildTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for $targetArch" + rootDirRel = config.rootDirRel?.let { File(it) } + target = targetName + release = profile == "release" + } + buildTask.dependsOn(targetBuildTask) + project.tasks.findByName("preBuild")?.mustRunAfter(targetBuildTask) + } + } + } + } +} diff --git a/plugins/dialog/examples/tauri-app/src/App.svelte b/plugins/dialog/examples/tauri-app/src/App.svelte index 363ba689..8faf9813 100644 --- a/plugins/dialog/examples/tauri-app/src/App.svelte +++ b/plugins/dialog/examples/tauri-app/src/App.svelte @@ -1,9 +1,19 @@
+
+ +
+ + diff --git a/plugins/dialog/examples/tauri-app/src/lib/FileDialogs.svelte b/plugins/dialog/examples/tauri-app/src/lib/FileDialogs.svelte new file mode 100644 index 00000000..7473a646 --- /dev/null +++ b/plugins/dialog/examples/tauri-app/src/lib/FileDialogs.svelte @@ -0,0 +1,27 @@ + + +
+
{response}
+ +
diff --git a/plugins/dialog/examples/tauri-app/yarn.lock b/plugins/dialog/examples/tauri-app/yarn.lock index 71a18912..4a8dac7c 100644 --- a/plugins/dialog/examples/tauri-app/yarn.lock +++ b/plugins/dialog/examples/tauri-app/yarn.lock @@ -24,70 +24,70 @@ svelte-hmr "^0.15.1" vitefu "^0.2.2" -"@tauri-apps/api@^1.1.0", "@tauri-apps/api@^1.2.0": +"@tauri-apps/api@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.2.0.tgz#1f196b3e012971227f41b98214c846430a4eb477" integrity sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw== -"@tauri-apps/cli-darwin-arm64@2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-alpha.6.tgz#d4fd5aa715bb3df7513f321636b3bcbade81fe06" - integrity sha512-Oc6EUaXRsAXCapl5EdEqkzMUkCpqQ9ELXhLORwgVTDNYjq11xpe/VxYlVoao/FuEp9DnHvcdZVlgNrEgWb8whQ== - -"@tauri-apps/cli-darwin-x64@2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-alpha.6.tgz#55455274f6860f2412f99a1b2603bffcdce691e7" - integrity sha512-HhF4XFTsznCzAVXQJd1e+yi1DdVD5PHOXVl2ZjeqmALvci4X/yCZEj4sCg4+Qukvs8BVnDYRvGs7M8cjbJZJoQ== - -"@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-alpha.6.tgz#88bce2a4714530447d43a0e8bf3bebbc6162980b" - integrity sha512-diHDgkBtx1exs/vlouy0B0MrUkJL0ojCJZcoq3rR76kMk2Gmsxz43AsoQfgziE9BCS2luIwilMuofgKOY4s+Zg== - -"@tauri-apps/cli-linux-arm64-gnu@2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-alpha.6.tgz#388bfa33cf7dc4ce6676f3be7ebe34fdbab7dfa2" - integrity sha512-g8SpZ3rVUdVIfuuosdnq3U359KF48R374b1G3xUUyebJAwWZ5Bay0P94RW0JNoqbM9Z4UpSedrCgIPcunnmZHw== - -"@tauri-apps/cli-linux-arm64-musl@2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-alpha.6.tgz#90059465acfd397aeba0d6a12d2bb6fbd6f0fc06" - integrity sha512-RD5u0MoBKruMZK7jp4tcQ9JUU6wx25+m0HfT+WSC3YGV8waRxVT/yxyxH5FEPhsxsT0LUQohOUF2S3oB4/bYIg== - -"@tauri-apps/cli-linux-x64-gnu@2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-alpha.6.tgz#6d0e6e371168ee38c0657e519f227d01e8e9f181" - integrity sha512-AWJbXJ8bZgwWDorHnCLdLKdz7j9sQqnhKaP+GTNZ2bBUuM03rVYfB5GAOmteqSAXOWfzT/LCLIZIQaZ+Y36vHg== - -"@tauri-apps/cli-linux-x64-musl@2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-alpha.6.tgz#ad4d978c8babaea424b502e901bf1be20eea2241" - integrity sha512-0+FoSopNCHdwEWmdYdlY+G8WXa2ojFXAqs1vIiJlQ4Z6e1Lv6dmPww+FmhNChW4bSi/HH1PbdPX0FB0WEFnM2g== - -"@tauri-apps/cli-win32-ia32-msvc@2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-alpha.6.tgz#5db864a62beecfa446db0e9af6afc152d8bdec32" - integrity sha512-JsdlWyP2szDvOXhNMSbDUMxb0t5ppl80AAtUjtNM47nyM2/QtoMYPZ6eIjxmKnss/ZbRCF5eCEgP1v5/x+W/gw== - -"@tauri-apps/cli-win32-x64-msvc@2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-alpha.6.tgz#7404db576ee00bbbf99399905b9fedaec48159f2" - integrity sha512-RY4aCCIyiwuD0iDxzwvqDSyoMoz6xgoGzWS4Xy2wKvoi/ptUAPRn8uhI3JTLFH4U+qky0KXO1oonBfOTF/pWQw== - -"@tauri-apps/cli@^2.0.0-alpha.6": - version "2.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-2.0.0-alpha.6.tgz#bcfef07161f9cf695485488f54559ae02f61c992" - integrity sha512-5apIpCGSuf5fUKYW1Jw0Qi1AAMvlUfr7jX41lhIrpXYeOD+Q8HKlpgpRpUScONfA9TThJBUuOfSVPbEkYMsyfw== +"@tauri-apps/cli-darwin-arm64@2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-alpha.7.tgz#976c4b24db7efced6c9b225f4c767f88fad3a375" + integrity sha512-Mv4GGMBUU8c31bZhQKRs00Yo39bj0kAfJHPiEld+oBGy/qs9PskM1f4GXjaBB0LTuzdgRfzgWLHR+EDP/2LYAQ== + +"@tauri-apps/cli-darwin-x64@2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-alpha.7.tgz#08aab5785c1591ea4a62d38c43099bce53c0d9fc" + integrity sha512-ucKCWJrALVx9gfWpKPF+oLdgHqk0HrbnNi/q8EtUcRoZ3lRRgKIMiiqD9tzjgx80IkFtXni3drBmBlP1J4sWiw== + +"@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-alpha.7.tgz#5970b5fd20efede79a685f09eb4c0c9427a11c46" + integrity sha512-U9Cv6/SN8IDRLaRIBYPKTIBKuRSwMhwsAeGqevz6JU2/Br9B6L6tiyurDBpsfUo67B0XdqX0/mJ5r3TlKuQknw== + +"@tauri-apps/cli-linux-arm64-gnu@2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-alpha.7.tgz#52ff0a852028643ade289a5a9a6e05207d5f60e2" + integrity sha512-rFDUif4DuRWkz9KkJGTekpY6k4FrJdRFrfcxHhaw16q1Q2V/+yIUnAxEDGcJNny+BnDbvsurFCrKOiIdC7BDZw== + +"@tauri-apps/cli-linux-arm64-musl@2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-alpha.7.tgz#ccdbe600044099b3c355947a599bffc0ef8001d2" + integrity sha512-0SObooHyStCtAtvGsmQm7dHeYWJgD+JECkl5hLRBp2sPIY+0iuyNcibhwTZzkwgOUzNlyY1usgRXYe7bmFunLA== + +"@tauri-apps/cli-linux-x64-gnu@2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-alpha.7.tgz#12a2894b07df723c7aaccd2f59b81cb76d29ebd9" + integrity sha512-nE4iT5tDp+sTyJxxuDvnAdiUtKRGel9QCZsAxUYbqixagogxqf9RZWg4vtcyspxDpynOIMwe0EONj/pAOXNmsQ== + +"@tauri-apps/cli-linux-x64-musl@2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-alpha.7.tgz#29ab9300b8c6ab68e3d2db8a8ce9ff4ba57b1e9a" + integrity sha512-78WoMrOjGUWmSgy2wRLrYov2mkvxjWjwTSre6Hy+gBUCVHfflxE9Lp8a+oEjkGZCO0oDVrLqAhZrxA4jaDFCKw== + +"@tauri-apps/cli-win32-ia32-msvc@2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-alpha.7.tgz#ce09752e5feaa1a69f6c5a0fb77b7b7d1c763a37" + integrity sha512-GNKzxb+3MvQd1pH5r42aGHQtv7RoGT2mnBUm4AnpvKI8qHTmyk/bza7X68zoHjtK+qRoESI2g1mtCq/DvyW43w== + +"@tauri-apps/cli-win32-x64-msvc@2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-alpha.7.tgz#c2790d6389c3f869e33689f3a630d76f7857bbe5" + integrity sha512-i4cpzbtb5sqIiv/+kpJ2nF6NO0RnG1kp9DIivMQjTIBsNa+lPL+q5f0vK5TJwUZArhmFM7RmoVmRYB3lq+T23w== + +"@tauri-apps/cli@^2.0.0-alpha.7": + version "2.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-2.0.0-alpha.7.tgz#0efa2c0d6be203da41f20d12a19ecc20d68fdcb6" + integrity sha512-BCIgUd/4FZeZzDXb+V0Bp+OuXtHmuxyRVEkA3i509e9+MSOKndUmtppn/5XmvxrJxf0GV7kXRTmVaDzl6w7O7w== optionalDependencies: - "@tauri-apps/cli-darwin-arm64" "2.0.0-alpha.6" - "@tauri-apps/cli-darwin-x64" "2.0.0-alpha.6" - "@tauri-apps/cli-linux-arm-gnueabihf" "2.0.0-alpha.6" - "@tauri-apps/cli-linux-arm64-gnu" "2.0.0-alpha.6" - "@tauri-apps/cli-linux-arm64-musl" "2.0.0-alpha.6" - "@tauri-apps/cli-linux-x64-gnu" "2.0.0-alpha.6" - "@tauri-apps/cli-linux-x64-musl" "2.0.0-alpha.6" - "@tauri-apps/cli-win32-ia32-msvc" "2.0.0-alpha.6" - "@tauri-apps/cli-win32-x64-msvc" "2.0.0-alpha.6" + "@tauri-apps/cli-darwin-arm64" "2.0.0-alpha.7" + "@tauri-apps/cli-darwin-x64" "2.0.0-alpha.7" + "@tauri-apps/cli-linux-arm-gnueabihf" "2.0.0-alpha.7" + "@tauri-apps/cli-linux-arm64-gnu" "2.0.0-alpha.7" + "@tauri-apps/cli-linux-arm64-musl" "2.0.0-alpha.7" + "@tauri-apps/cli-linux-x64-gnu" "2.0.0-alpha.7" + "@tauri-apps/cli-linux-x64-musl" "2.0.0-alpha.7" + "@tauri-apps/cli-win32-ia32-msvc" "2.0.0-alpha.7" + "@tauri-apps/cli-win32-x64-msvc" "2.0.0-alpha.7" cross-spawn@^7.0.3: version "7.0.3" diff --git a/plugins/dialog/guest-js/index.ts b/plugins/dialog/guest-js/index.ts index aeb09e51..09270f6e 100644 --- a/plugins/dialog/guest-js/index.ts +++ b/plugins/dialog/guest-js/index.ts @@ -4,6 +4,18 @@ import { invoke } from '@tauri-apps/api/tauri' +interface FileResponse { + base64Data?: string + duration?: number + height?: number + width?: number + mimeType?: string + modifiedAt?: number + name?: string + path: string + size: number +} + /** * Extension filters for the file dialog. * @@ -86,6 +98,18 @@ interface ConfirmDialogOptions { cancelLabel?: string } +async function open( + options?: OpenDialogOptions & { multiple?: false, directory?: false } +): Promise +async function open( + options?: OpenDialogOptions & { multiple?: true, directory?: false } +): Promise +async function open( + options?: OpenDialogOptions & { multiple?: false, directory?: true } +): Promise +async function open( + options?: OpenDialogOptions & { multiple?: true, directory?: true } +): Promise /** * Open a file/directory selection dialog. * @@ -140,7 +164,7 @@ interface ConfirmDialogOptions { */ async function open( options: OpenDialogOptions = {} -): Promise { +): Promise { if (typeof options === 'object') { Object.freeze(options) } diff --git a/plugins/dialog/src/commands.rs b/plugins/dialog/src/commands.rs index bf2b7b27..ad008460 100644 --- a/plugins/dialog/src/commands.rs +++ b/plugins/dialog/src/commands.rs @@ -7,13 +7,15 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use tauri::{command, Manager, Runtime, State, Window}; -use crate::{Dialog, FileDialogBuilder, MessageDialogKind, Result}; +use crate::{Dialog, FileDialogBuilder, FileResponse, MessageDialogKind, Result}; #[derive(Serialize)] #[serde(untagged)] pub enum OpenResponse { - Multiple(Option>), - Path(Option), + Folders(Option>), + Folder(Option), + Files(Option>), + File(Option), } #[allow(dead_code)] @@ -102,69 +104,80 @@ pub(crate) async fn open( } let res = if options.directory { - if options.multiple { - let folders = dialog_builder.blocking_pick_folders(); - if let Some(folders) = &folders { - for folder in folders { - window - .fs_scope() - .allow_directory(folder, options.recursive)?; + #[cfg(desktop)] + { + if options.multiple { + let folders = dialog_builder.blocking_pick_folders(); + if let Some(folders) = &folders { + for folder in folders { + window + .fs_scope() + .allow_directory(folder, options.recursive)?; + } } + OpenResponse::Folders(folders) + } else { + let folder = dialog_builder.blocking_pick_folder(); + if let Some(path) = &folder { + window.fs_scope().allow_directory(path, options.recursive)?; + } + OpenResponse::Folder(folder) } - OpenResponse::Multiple(folders) - } else { - let folder = dialog_builder.blocking_pick_folder(); - if let Some(path) = &folder { - window.fs_scope().allow_directory(path, options.recursive)?; - } - OpenResponse::Path(folder) } + #[cfg(mobile)] + return Err(crate::Error::FolderPickerNotImplemented); } else if options.multiple { let files = dialog_builder.blocking_pick_files(); if let Some(files) = &files { for file in files { - window.fs_scope().allow_file(file)?; + window.fs_scope().allow_file(&file.path)?; } } - OpenResponse::Multiple(files) + OpenResponse::Files(files) } else { let file = dialog_builder.blocking_pick_file(); if let Some(file) = &file { - window.fs_scope().allow_file(file)?; + window.fs_scope().allow_file(&file.path)?; } - OpenResponse::Path(file) + OpenResponse::File(file) }; Ok(res) } +#[allow(unused_variables)] #[command] pub(crate) async fn save( window: Window, dialog: State<'_, Dialog>, options: SaveDialogOptions, ) -> Result> { - let mut dialog_builder = dialog.file(); - #[cfg(any(windows, target_os = "macos"))] + #[cfg(mobile)] + return Err(crate::Error::FileSaveDialogNotImplemented); + #[cfg(desktop)] { - dialog_builder = dialog_builder.set_parent(&window); - } - if let Some(title) = options.title { - dialog_builder = dialog_builder.set_title(&title); - } - if let Some(default_path) = options.default_path { - dialog_builder = set_default_path(dialog_builder, default_path); - } - for filter in options.filters { - let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect(); - dialog_builder = dialog_builder.add_filter(filter.name, &extensions); - } + let mut dialog_builder = dialog.file(); + #[cfg(any(windows, target_os = "macos"))] + { + dialog_builder = dialog_builder.set_parent(&window); + } + if let Some(title) = options.title { + dialog_builder = dialog_builder.set_title(&title); + } + if let Some(default_path) = options.default_path { + dialog_builder = set_default_path(dialog_builder, default_path); + } + for filter in options.filters { + let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect(); + dialog_builder = dialog_builder.add_filter(filter.name, &extensions); + } - let path = dialog_builder.blocking_save_file(); - if let Some(p) = &path { - window.fs_scope().allow_file(p)?; - } + let path = dialog_builder.blocking_save_file(); + if let Some(p) = &path { + window.fs_scope().allow_file(p)?; + } - Ok(path) + Ok(path) + } } fn message_dialog( diff --git a/plugins/dialog/src/error.rs b/plugins/dialog/src/error.rs index 9918a184..7aa9804b 100644 --- a/plugins/dialog/src/error.rs +++ b/plugins/dialog/src/error.rs @@ -15,6 +15,12 @@ pub enum Error { #[cfg(mobile)] #[error(transparent)] PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), + #[cfg(mobile)] + #[error("Folder picker is not implemented on mobile")] + FolderPickerNotImplemented, + #[cfg(mobile)] + #[error("File save dialog is not implemented on mobile")] + FileSaveDialogNotImplemented, } impl Serialize for Error { diff --git a/plugins/dialog/src/lib.rs b/plugins/dialog/src/lib.rs index cc40c1e9..a0d4ba15 100644 --- a/plugins/dialog/src/lib.rs +++ b/plugins/dialog/src/lib.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use serde::Serialize; +use serde::{Deserialize, Serialize}; use tauri::{ plugin::{Builder, TauriPlugin}, Manager, Runtime, @@ -191,6 +191,36 @@ impl MessageDialogBuilder { } } +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FileResponse { + pub base64_data: Option, + pub duration: Option, + pub height: Option, + pub width: Option, + pub mime_type: Option, + pub modified_at: Option, + pub name: Option, + pub path: PathBuf, + pub size: u64, +} + +impl FileResponse { + fn new(path: PathBuf) -> Self { + Self { + base64_data: None, + duration: None, + height: None, + width: None, + mime_type: None, + modified_at: None, + name: path.file_name().map(|f| f.to_string_lossy().into_owned()), + path, + size: 0, + } + } +} + #[derive(Debug, Serialize)] pub(crate) struct Filter { pub name: String, @@ -217,9 +247,7 @@ pub struct FileDialogBuilder { #[serde(rename_all = "camelCase")] pub struct FileDialogPayload<'a> { filters: &'a Vec, - starting_directory: &'a Option, - file_name: &'a Option, - title: &'a Option, + multiple: bool, } // raw window handle :( @@ -240,12 +268,10 @@ impl FileDialogBuilder { } #[cfg(mobile)] - pub(crate) fn payload(&self) -> FileDialogPayload<'_> { + pub(crate) fn payload(&self, multiple: bool) -> FileDialogPayload<'_> { FileDialogPayload { filters: &self.filters, - starting_directory: &self.starting_directory, - file_name: &self.file_name, - title: &self.title, + multiple, } } @@ -308,7 +334,9 @@ impl FileDialogBuilder { /// }) /// }) /// ``` - pub fn pick_file) + Send + 'static>(self, f: F) { + pub fn pick_file) + Send + 'static>(self, f: F) { + #[cfg(desktop)] + let f = |path: Option| f(path.map(FileResponse::new)); pick_file(self, f) } @@ -330,7 +358,15 @@ impl FileDialogBuilder { /// }) /// }) /// ``` - pub fn pick_files>) + Send + 'static>(self, f: F) { + pub fn pick_files>) + Send + 'static>(self, f: F) { + #[cfg(desktop)] + let f = |paths: Option>| { + f(paths.map(|p| { + p.into_iter() + .map(FileResponse::new) + .collect::>() + })) + }; pick_files(self, f) } @@ -352,6 +388,7 @@ impl FileDialogBuilder { /// }) /// }) /// ``` + #[cfg(desktop)] pub fn pick_folder) + Send + 'static>(self, f: F) { pick_folder(self, f) } @@ -374,6 +411,7 @@ impl FileDialogBuilder { /// }) /// }) /// ``` + #[cfg(desktop)] pub fn pick_folders>) + Send + 'static>(self, f: F) { pick_folders(self, f) } @@ -397,6 +435,7 @@ impl FileDialogBuilder { /// }) /// }) /// ``` + #[cfg(desktop)] pub fn save_file) + Send + 'static>(self, f: F) { save_file(self, f) } @@ -419,7 +458,7 @@ impl FileDialogBuilder { /// // the file path is `None` if the user closed the dialog /// } /// ``` - pub fn blocking_pick_file(self) -> Option { + pub fn blocking_pick_file(self) -> Option { blocking_fn!(self, pick_file) } @@ -438,7 +477,7 @@ impl FileDialogBuilder { /// // the file paths value is `None` if the user closed the dialog /// } /// ``` - pub fn blocking_pick_files(self) -> Option> { + pub fn blocking_pick_files(self) -> Option> { blocking_fn!(self, pick_files) } @@ -457,6 +496,7 @@ impl FileDialogBuilder { /// // the folder path is `None` if the user closed the dialog /// } /// ``` + #[cfg(desktop)] pub fn blocking_pick_folder(self) -> Option { blocking_fn!(self, pick_folder) } @@ -476,6 +516,7 @@ impl FileDialogBuilder { /// // the folder paths value is `None` if the user closed the dialog /// } /// ``` + #[cfg(desktop)] pub fn blocking_pick_folders(self) -> Option> { blocking_fn!(self, pick_folders) } @@ -495,6 +536,7 @@ 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) } diff --git a/plugins/dialog/src/mobile.rs b/plugins/dialog/src/mobile.rs index 05d9f375..703186c0 100644 --- a/plugins/dialog/src/mobile.rs +++ b/plugins/dialog/src/mobile.rs @@ -10,7 +10,7 @@ use tauri::{ AppHandle, Runtime, }; -use crate::{FileDialogBuilder, MessageDialogBuilder}; +use crate::{FileDialogBuilder, FileResponse, MessageDialogBuilder}; #[cfg(target_os = "android")] const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog"; @@ -46,59 +46,43 @@ impl Dialog { } } -pub fn pick_file) + Send + 'static>( - dialog: FileDialogBuilder, - f: F, -) { - let res = dialog - .dialog - .0 - .run_mobile_plugin::>("pickFile", dialog.payload()); - f(res.unwrap_or_default()) -} - -pub fn pick_files>) + Send + 'static>( - dialog: FileDialogBuilder, - f: F, -) { - let res = dialog - .dialog - .0 - .run_mobile_plugin::>>("pickFiles", dialog.payload()); - f(res.unwrap_or_default()) -} - -pub fn pick_folder) + Send + 'static>( - dialog: FileDialogBuilder, - f: F, -) { - let res = dialog - .dialog - .0 - .run_mobile_plugin::>("pickFolder", dialog.payload()); - f(res.unwrap_or_default()) +#[derive(Debug, Deserialize)] +struct FilePickerResponse { + files: Vec, } -pub fn pick_folders>) + Send + 'static>( +pub fn pick_file) + Send + 'static>( dialog: FileDialogBuilder, f: F, ) { - let res = dialog - .dialog - .0 - .run_mobile_plugin::>>("pickFolders", dialog.payload()); - f(res.unwrap_or_default()) + std::thread::spawn(move || { + let res = dialog + .dialog + .0 + .run_mobile_plugin::("showFilePicker", dialog.payload(false)); + if let Ok(response) = res { + f(Some(response.files.into_iter().next().unwrap())) + } else { + f(None) + } + }); } -pub fn save_file) + Send + 'static>( +pub fn pick_files>) + Send + 'static>( dialog: FileDialogBuilder, f: F, ) { - let res = dialog - .dialog - .0 - .run_mobile_plugin::>("saveFile", dialog.payload()); - f(res.unwrap_or_default()) + std::thread::spawn(move || { + let res = dialog + .dialog + .0 + .run_mobile_plugin::("showFilePicker", dialog.payload(true)); + if let Ok(response) = res { + f(Some(response.files)) + } else { + f(None) + } + }); } #[derive(Debug, Deserialize)] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b16b812..e8bbd771 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,9 +79,6 @@ importers: plugins/dialog/examples/tauri-app: dependencies: - '@tauri-apps/api': - specifier: ^1.1.0 - version: 1.2.0 tauri-plugin-dialog-api: specifier: link:../../ version: link:../..