parent
bf03316a04
commit
778c99aa0c
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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<String>? = null
|
||||||
|
var arches: List<String>? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
open class RustPlugin : Plugin<Project> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,19 @@
|
|||||||
<script>
|
<script>
|
||||||
import MessageDialogs from "./lib/MessageDialogs.svelte";
|
import MessageDialogs from "./lib/MessageDialogs.svelte";
|
||||||
|
import FileDialogs from "./lib/FileDialogs.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<MessageDialogs />
|
<MessageDialogs />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<FileDialogs />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container > .row {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
<script>
|
||||||
|
import * as dialog from "tauri-plugin-dialog-api";
|
||||||
|
|
||||||
|
let response = "";
|
||||||
|
|
||||||
|
async function open() {
|
||||||
|
try {
|
||||||
|
const res = await dialog.open({
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
extensions: ["image/jpg"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
multiple: false,
|
||||||
|
});
|
||||||
|
response = JSON.stringify(res, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>{response}</div>
|
||||||
|
<button on:click={open}> Open </button>
|
||||||
|
</div>
|
Loading…
Reference in new issue