Skip to content

Commit

Permalink
Merge pull request #206 from BioforestChain/nativeFileChooser
Browse files Browse the repository at this point in the history
feat: add native file chooser
  • Loading branch information
kingsword09 authored Aug 12, 2024
2 parents 60f5133 + 4983d66 commit 09aa34f
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.dweb_browser.browser.scan

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Camera
import androidx.compose.material3.AlertDialog
Expand All @@ -12,18 +11,18 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.graphics.toComposeImageBitmap
import io.github.vinceglb.filekit.compose.rememberFilePickerLauncher
import io.github.vinceglb.filekit.core.PickerType
import io.github.vinceglb.filekit.core.PlatformDirectory
import kotlinx.coroutines.launch
import org.dweb_browser.browser.BrowserI18nResource
import org.dweb_browser.browser.common.loading.LoadingView
import org.dweb_browser.helper.globalDefaultScope
import org.jetbrains.skia.Image
import java.io.File
import javax.swing.JFileChooser
import javax.swing.filechooser.FileNameExtensionFilter


// 记忆最后一次打开的路径
Expand Down Expand Up @@ -104,20 +103,16 @@ actual fun AlbumPreviewRender(
controller: SmartScanController
) {
// 渲染文件选择
SwingPanel(
modifier = Modifier.fillMaxSize(),
factory = {
val fileChooser = JFileChooser().apply {
fileSelectionMode = JFileChooser.FILES_ONLY
fileFilter = FileNameExtensionFilter("Image Files", "jpg", "jpeg", "png", "gif", "bmp")
lastDirectory.value?.let {
currentDirectory = File(it)
}
}
fileChooser.addActionListener { event ->
if (event.actionCommand == JFileChooser.APPROVE_SELECTION) {
lastDirectory.value = fileChooser.currentDirectory.absolutePath
val byteArray = fileChooser.selectedFile.readBytes()
val scope = rememberCoroutineScope()
val directory: PlatformDirectory? by remember { mutableStateOf(null) }
val singleFilePicker = rememberFilePickerLauncher(
type = PickerType.Image,
title = BrowserI18nResource.QRCode.select_QR_code.text,
initialDirectory = directory?.path,
onResult = { file ->
file?.let {
scope.launch {
val byteArray = file.readBytes()
val image = Image.makeFromEncoded(byteArray)
// 回调图片
controller.albumImageFlow.tryEmit(image.toComposeImageBitmap())
Expand All @@ -127,11 +122,12 @@ actual fun AlbumPreviewRender(
recognize(byteArray, 0)
}
}
} else {
controller.onCancel(event.actionCommand)
}
}
fileChooser
}
)

LaunchedEffect(Unit) {
singleFilePicker.launch()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ class DWebViewEngine internal constructor(
val downloadSignal = setupDownloadSignal(this)
internal val createWebMessagePortPicker = setupWebMessagePicker(this)
val iconBitmapFlow = setupIconBitmapFlow(this)
internal val fileChooser = setupFileChooser(this)

init {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.dweb_browser.dwebview.engine

import com.teamdev.jxbrowser.browser.callback.OpenFileCallback
import com.teamdev.jxbrowser.browser.callback.OpenFilesCallback
import com.teamdev.jxbrowser.browser.callback.OpenFolderCallback
import kotlinx.coroutines.launch
import org.dweb_browser.sys.filechooser.FileChooserManage
import java.nio.file.Path

fun setupFileChooser(engine: DWebViewEngine) {
val fileChooserManage = FileChooserManage()
engine.browser.set(OpenFileCallback::class.java, OpenFileCallback { params, tell ->
val acceptableExtensions: List<String> = params.acceptableExtensions()

engine.lifecycleScope.launch {
val pickerFiles =
fileChooserManage.openFileChooser(engine.remoteMM, acceptableExtensions, false)
pickerFiles.firstOrNull()?.let {
tell.open(Path.of(it))
} ?: tell.cancel()
}
})

engine.browser.set(OpenFilesCallback::class.java, OpenFilesCallback { params, tell ->
val acceptableExtensions: List<String> = params.acceptableExtensions()
engine.lifecycleScope.launch {
val pickerFiles =
fileChooserManage.openFileChooser(engine.remoteMM, acceptableExtensions, true)

if (pickerFiles.isEmpty()) tell.cancel() else
tell.open(*pickerFiles.map { Path.of(it) }.toTypedArray())
}
})

engine.browser.set(OpenFolderCallback::class.java, OpenFolderCallback { params, tell ->
engine.lifecycleScope.launch {
val folder = fileChooserManage.openFolderChooser(params.suggestedDirectory())

if (folder.isEmpty()) tell.cancel() else tell.open(Path.of(folder))
}
})
}
4 changes: 4 additions & 0 deletions next/kmp/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ benchmark-macro-junit4 = "1.2.4"
roomKtx = "2.6.1"
log4j12 = "2.0.12"

filekit = "0.7.0"

[libraries]

# Compose
Expand Down Expand Up @@ -223,6 +225,8 @@ androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiaut
benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmark-macro-junit4" }
org-slf4j-log4j12 = { group = "org.slf4j", name = "slf4j-log4j12", version.ref = "log4j12" }

# 文件选择器
filekit-compose = { group = "io.github.vinceglb", name = "filekit-compose", version.ref = "filekit" }

# jna
java-jna = { module = "net.java.dev.jna:jna", version.ref = "java-jna-version" }
Expand Down
3 changes: 3 additions & 0 deletions next/kmp/sys/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ kotlin {
// 不直接使用 projects.*,因为如果 disabled 了 desktop,那么就会解析不过
implementation(project(":lib_biometrics"))
implementation(project(":lib_hardware_info"))

// 文件选择器
api(libs.filekit.compose)
}
}
sourceSets.create("nativeJvmMain") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ class FileChooserActivity : ComponentActivity() {
private const val EXTRA_TASK_ID_KEY = "taskId"
private const val EXTRA_ACCEPT_KEY = "accept"
private const val EXTRA_MULTI_KEY = "multiple"
private const val EXTRA_LIMIT_KEY = "limit"

suspend fun launchAndroidFileChooser(
microModule: MicroModule.Runtime, mimeType: String, multiple: Boolean, limit: Int
microModule: MicroModule.Runtime, mimeType: String, multiple: Boolean
): List<String> {
val taskId = randomUUID()
return CompletableDeferred<List<String>>().also { task ->
Expand All @@ -34,7 +33,6 @@ class FileChooserActivity : ComponentActivity() {
intent.putExtra(EXTRA_TASK_ID_KEY, taskId)
intent.putExtra(EXTRA_ACCEPT_KEY, mimeType)
intent.putExtra(EXTRA_MULTI_KEY, multiple)
intent.putExtra(EXTRA_LIMIT_KEY, limit)
}
}.await()
}
Expand All @@ -45,7 +43,6 @@ class FileChooserActivity : ComponentActivity() {
val taskId = intent.getStringExtra(EXTRA_TASK_ID_KEY) ?: finish()
val accept = intent.getStringExtra(EXTRA_ACCEPT_KEY)!!
val multiple = intent.getBooleanExtra(EXTRA_MULTI_KEY, false)
val limit = intent.getIntExtra(EXTRA_LIMIT_KEY, 1)

lifecycleScope.launch {
when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import org.dweb_browser.core.module.MicroModule

actual class FileChooserManage {
actual suspend fun openFileChooser(
microModule: MicroModule.Runtime, accept: String, multiple: Boolean, limit: Int
microModule: MicroModule.Runtime, accept: String, multiple: Boolean
): List<String> {
return FileChooserActivity.launchAndroidFileChooser(microModule, accept, multiple, limit)
return FileChooserActivity.launchAndroidFileChooser(microModule, accept, multiple)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.dweb_browser.helper.some

expect class FileChooserManage() {
suspend fun openFileChooser(
microModule: MicroModule.Runtime, accept: String, multiple: Boolean, limit: Int
microModule: MicroModule.Runtime, accept: String, multiple: Boolean
): List<String>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ class FileChooserNMM : NativeMicroModule("fs-picker.sys.dweb", "FileChooser") {
// accept 的格式为mime:ext,ext;mime:ext,比如:image/*:.jpg,.png;video/*:.mp4
val accept = request.queryOrNull("accept") ?: "*/*"
val multiple = request.queryOrNull("multiple")?.toBoolean() ?: false
val limit = request.queryOrNull("limit")?.toInt() ?: 1
debugFileChooser("open-file", "accept=$accept, multiple=$multiple, limit=$limit")
debugFileChooser("open-file", "accept=$accept, multiple=$multiple")
val fromMM = getRemoteRuntime()
fileChooserManage.openFileChooser(fromMM, accept, multiple, limit).toJsonElement()
fileChooserManage.openFileChooser(fromMM, accept, multiple).toJsonElement()
},
"/directory" bind PureMethod.GET by defineEmptyResponse {
// /directory?mode=*&startIn=*&preference=* 选择目录
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,56 @@
package org.dweb_browser.sys.filechooser

import io.github.vinceglb.filekit.core.FileKit
import io.github.vinceglb.filekit.core.PickerMode
import io.github.vinceglb.filekit.core.PickerType
import io.github.vinceglb.filekit.core.pickFile
import org.dweb_browser.core.module.MicroModule
import org.dweb_browser.helper.platform.awaitComposeWindow
import java.io.File
import javax.swing.JFileChooser
import javax.swing.filechooser.FileFilter

actual class FileChooserManage actual constructor() {
actual suspend fun openFileChooser(
microModule: MicroModule.Runtime,
accept: String,
multiple: Boolean,
limit: Int
multiple: Boolean
): List<String> {
return when (val composeWindow = microModule.awaitComposeWindow()) {
null -> emptyList()
else -> {
val fc = JFileChooser();
fc.isMultiSelectionEnabled = multiple
val fileNameFilter = acceptToNameFilter(accept)
fc.addChoosableFileFilter(object : FileFilter() {
override fun accept(file: File): Boolean {
return fileNameFilter(file.name)
}

override fun getDescription(): String {
return accept
}
})
when (fc.showOpenDialog(composeWindow)) {
JFileChooser.APPROVE_OPTION -> {
fc.selectedFiles.map { it.absolutePath }
}

else -> emptyList()
}
}
val pickerType = when (accept) {
"image/*" -> PickerType.Image
"video/*" -> PickerType.Video
else -> PickerType.File()
}

if (multiple) {
val pickFiles = FileKit.pickFile(pickerType, mode = PickerMode.Multiple())

return pickFiles?.map {
it.file.absolutePath
} ?: emptyList()
} else {
val pickFile = FileKit.pickFile(pickerType)

return pickFile?.file?.absolutePath?.let { listOf(it) } ?: emptyList()
}
}

suspend fun openFileChooser(
microModule: MicroModule.Runtime,
accept: List<String>,
multiple: Boolean
): List<String> {
val pickerType = PickerType.File(extensions = accept)

if (multiple) {
val pickFiles = FileKit.pickFile(pickerType, mode = PickerMode.Multiple())

return pickFiles?.map {
it.file.absolutePath
} ?: emptyList()
} else {
val pickFile = FileKit.pickFile(pickerType)

return pickFile?.file?.absolutePath?.let { listOf(it) } ?: emptyList()
}
}

suspend fun openFolderChooser(initialDirectory: String?) =
FileKit.pickDirectory(initialDirectory = initialDirectory)?.file?.absolutePath ?: ""
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ actual class MediaCaptureManage actual constructor() {
microModule: MicroModule.Runtime,
accept: String
) =
fileChooser.openFileChooser(microModule, "image/*", false, 1).firstOrNull()?.let {
fileChooser.openFileChooser(microModule, accept, false).firstOrNull()?.let {
val file = File(it)
PureStream(file.inputStream().toByteReadChannel())
}
Expand Down

0 comments on commit 09aa34f

Please sign in to comment.