feat(send): multi-file shares and receivers polish

Allow selecting multiple files on Android, desktop, and iOS and share them
as one transfer through the core multi-source path. Update the composer UI
for multi-file review, hide unfinished iOS receive methods, and surface
refusal reasons on the receivers list.
This commit is contained in:
2026-07-12 17:03:53 +02:00
parent ceec383473
commit d3a5f19ecb
21 changed files with 285 additions and 115 deletions

View File

@@ -15,13 +15,14 @@ import javax.swing.filechooser.FileSystemView
@Composable
actual fun rememberShareFilePicker(
onFilePicked: (PickedShareFile) -> Unit,
onFilesPicked: (List<PickedShareFile>) -> Unit,
onError: (String) -> Unit,
): ShareFilePicker = remember(onFilePicked, onError) {
): ShareFilePicker = remember(onFilesPicked, onError) {
object : ShareFilePicker {
override fun pickFile() {
override fun pickFiles() {
openPicker(onError) {
pickShareFile()?.let(onFilePicked)
val selected = pickShareFiles()
if (selected.isNotEmpty()) onFilesPicked(selected)
}
}
}
@@ -71,22 +72,24 @@ private fun activeFrame(): Frame? {
?: Frame.getFrames().firstOrNull { it.isVisible }
}
private fun pickShareFile(): PickedShareFile? {
val dialog = nativeFileDialog("Select file to share")
private fun pickShareFiles(): List<PickedShareFile> {
val dialog = nativeFileDialog("Select files to share").apply {
isMultipleMode = true
}
return try {
dialog.isVisible = true
val directory = dialog.directory
val file = dialog.file
if (directory != null && file != null) {
val selected = File(directory, file)
val directory = dialog.directory ?: return emptyList()
val names = dialog.files?.map { it.name }.orEmpty().ifEmpty {
dialog.file?.let { listOf(it) }.orEmpty()
}
names.map { name ->
val selected = File(directory, name)
PickedShareFile(
selected.absolutePath,
selected.name,
selected.length().takeIf { it >= 0L }?.toULong(),
selected.systemIconPng(),
)
} else {
null
}
} finally {
dialog.dispose()

View File

@@ -28,11 +28,22 @@ private class JvmFileSystemService : FileSystemService {
override fun createReceiveOutputSink(folder: ReceiveFolder): ReceiveOutputSink? = null
override suspend fun sharePickedFile(
override suspend fun sharePickedFiles(
repository: CoreGateway,
file: PickedShareFile,
files: List<PickedShareFile>,
transferName: String,
senderName: String,
accessPolicy: ShareAccessPolicy,
): Result<Share> = repository.sharePath(file.value, transferName, senderName, accessPolicy)
): Result<Share> {
require(files.isNotEmpty()) { "Select at least one file to share" }
val sources = files.map { file ->
uniffi.vnidrop.ShareSource(
kind = uniffi.vnidrop.SourceKind.PATH,
value = file.value,
displayName = file.displayName,
isDirectory = false,
)
}
return repository.shareSources(sources, transferName, senderName, accessPolicy)
}
}