diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt index c044e0ad8b..213d05dcb0 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt @@ -32,6 +32,15 @@ actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> return FileChooserLauncher(launcher) } +@Composable +actual fun rememberFileChooserMultipleLauncher(onResult: (List) -> Unit): FileChooserMultipleLauncher { + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetMultipleContents(), + onResult = { onResult(it.map { it.toURI() }) } + ) + return FileChooserMultipleLauncher(launcher) +} + actual class FileChooserLauncher actual constructor() { private lateinit var launcher: ManagedActivityResultLauncher @@ -44,5 +53,17 @@ actual class FileChooserLauncher actual constructor() { } } +actual class FileChooserMultipleLauncher actual constructor() { + private lateinit var launcher: ManagedActivityResultLauncher> + + constructor(launcher: ManagedActivityResultLauncher>): this() { + this.launcher = launcher + } + + actual suspend fun launch(input: String) { + launcher.launch(input) + } +} + actual fun URI.inputStream(): InputStream? = androidAppContext.contentResolver.openInputStream(toUri()) actual fun URI.outputStream(): OutputStream = androidAppContext.contentResolver.openOutputStream(toUri())!! diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.android.kt new file mode 100644 index 0000000000..3dace5f9bc --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.android.kt @@ -0,0 +1,30 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import chat.simplex.common.views.newchat.ActionButton +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +actual fun ChooseAttachmentButtons(attachmentOption: MutableState, hide: () -> Unit) { + ActionButton(Modifier.fillMaxWidth(0.25f), null, stringResource(MR.strings.use_camera_button), icon = painterResource(MR.images.ic_camera_enhance)) { + attachmentOption.value = AttachmentOption.CameraPhoto + hide() + } + ActionButton(Modifier.fillMaxWidth(0.33f), null, stringResource(MR.strings.gallery_image_button), icon = painterResource(MR.images.ic_add_photo)) { + attachmentOption.value = AttachmentOption.GalleryImage + hide() + } + ActionButton(Modifier.fillMaxWidth(0.50f), null, stringResource(MR.strings.gallery_video_button), icon = painterResource(MR.images.ic_smart_display)) { + attachmentOption.value = AttachmentOption.GalleryVideo + hide() + } + ActionButton(Modifier.fillMaxWidth(1f), null, stringResource(MR.strings.choose_file), icon = painterResource(MR.images.ic_note_add)) { + attachmentOption.value = AttachmentOption.File + hide() + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt index ccaeef416d..b02c86579f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt @@ -71,9 +71,15 @@ fun getLoadedFilePath(file: CIFile?): String? { @Composable expect fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher +expect fun rememberFileChooserMultipleLauncher(onResult: (List) -> Unit): FileChooserMultipleLauncher + expect class FileChooserLauncher() { suspend fun launch(input: String) } +expect class FileChooserMultipleLauncher() { + suspend fun launch(input: String) +} + expect fun URI.inputStream(): InputStream? expect fun URI.outputStream(): OutputStream diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index c16a252a9c..6912ced0e8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -175,12 +175,12 @@ fun ComposeView( val content = ArrayList() val imagesPreview = ArrayList() uris.forEach { uri -> - var bitmap: ImageBitmap? = null + var bitmap: ImageBitmap? when { isImage(uri) -> { // Image val drawable = getDrawableFromUri(uri) - bitmap = if (drawable != null) getBitmapFromUri(uri) else null + bitmap = getBitmapFromUri(uri) if (isAnimImage(uri, drawable)) { // It's a gif or webp val fileSize = getFileSize(uri) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt index 3ad019949a..3963c69ae3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt @@ -34,7 +34,7 @@ fun ChatArchiveView(m: ChatModel, title: String, archiveName: String, archiveTim ChatArchiveLayout( title, archiveTime, - saveArchive = { withApi { saveArchiveLauncher.launch(archivePath.substringAfterLast("/")) }}, + saveArchive = { withApi { saveArchiveLauncher.launch(archivePath.substringAfterLast(File.separator)) }}, deleteArchiveAlert = { deleteArchiveAlert(m, archivePath) } ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 277d3ebe67..32d2379f75 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -457,7 +457,7 @@ private fun exportArchive( try { val archiveFile = exportChatArchive(m, chatArchiveName, chatArchiveTime, chatArchiveFile) chatArchiveFile.value = archiveFile - saveArchiveLauncher.launch(archiveFile.substringAfterLast("/")) + saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) progressIndicator.value = false } catch (e: Error) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_exporting_chat_database), e.toString()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt index 5eee8d99d2..aa3c4560ea 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt @@ -5,11 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.common.views.newchat.ActionButton -import chat.simplex.res.MR sealed class AttachmentOption { object CameraPhoto: AttachmentOption() @@ -19,10 +15,7 @@ sealed class AttachmentOption { } @Composable -fun ChooseAttachmentView( - attachmentOption: MutableState, - hide: () -> Unit -) { +fun ChooseAttachmentView(attachmentOption: MutableState, hide: () -> Unit) { Box( modifier = Modifier .fillMaxWidth() @@ -37,22 +30,10 @@ fun ChooseAttachmentView( .padding(horizontal = 8.dp, vertical = 30.dp), horizontalArrangement = Arrangement.SpaceEvenly ) { - ActionButton(Modifier.fillMaxWidth(0.25f), null, stringResource(MR.strings.use_camera_button), icon = painterResource(MR.images.ic_camera_enhance)) { - attachmentOption.value = AttachmentOption.CameraPhoto - hide() - } - ActionButton(Modifier.fillMaxWidth(0.33f), null, stringResource(MR.strings.gallery_image_button), icon = painterResource(MR.images.ic_add_photo)) { - attachmentOption.value = AttachmentOption.GalleryImage - hide() - } - ActionButton(Modifier.fillMaxWidth(0.50f), null, stringResource(MR.strings.gallery_video_button), icon = painterResource(MR.images.ic_smart_display)) { - attachmentOption.value = AttachmentOption.GalleryVideo - hide() - } - ActionButton(Modifier.fillMaxWidth(1f), null, stringResource(MR.strings.choose_file), icon = painterResource(MR.images.ic_note_add)) { - attachmentOption.value = AttachmentOption.File - hide() - } + ChooseAttachmentButtons(attachmentOption, hide) } } } + +@Composable +expect fun ChooseAttachmentButtons(attachmentOption: MutableState, hide: () -> Unit) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 5da9bd973c..064c62d3cd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -394,6 +394,7 @@ Camera From Gallery File + Choose a file Image Video diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index ffe9bd81a6..4c5cfb2526 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -38,8 +38,20 @@ fun showApp() = application { FileDialogChooser( title = "SimpleX", isLoad = true, + params = simplexWindowState.openDialog.params, onResult = { - simplexWindowState.openDialog.onResult(it) + simplexWindowState.openDialog.onResult(it.firstOrNull()) + } + ) + } + + if (simplexWindowState.openMultipleDialog.isAwaiting) { + FileDialogChooser( + title = "SimpleX", + isLoad = true, + params = simplexWindowState.openMultipleDialog.params, + onResult = { + simplexWindowState.openMultipleDialog.onResult(it) } ) } @@ -48,7 +60,8 @@ fun showApp() = application { FileDialogChooser( title = "SimpleX", isLoad = false, - onResult = { simplexWindowState.saveDialog.onResult(it) } + params = simplexWindowState.saveDialog.params, + onResult = { simplexWindowState.saveDialog.onResult(it.firstOrNull()) } ) } val toasts = remember { simplexWindowState.toasts } @@ -97,16 +110,25 @@ fun showApp() = application { class SimplexWindowState { val backstack = mutableStateListOf<() -> Unit>() val openDialog = DialogState() + val openMultipleDialog = DialogState>() val saveDialog = DialogState() val toasts = mutableStateListOf>() } +data class DialogParams( + val allowMultiple: Boolean = false, + val fileFilter: ((File?) -> Boolean)? = null, + val fileFilterDescription: String = "", +) + class DialogState { private var onResult: CompletableDeferred? by mutableStateOf(null) + var params = DialogParams() val isAwaiting get() = onResult != null - suspend fun awaitResult(): T { + suspend fun awaitResult(params: DialogParams = DialogParams()): T { onResult = CompletableDeferred() + this.params = params val result = onResult!!.await() onResult = null return result diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt index 9792f57726..023683fece 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt @@ -1,8 +1,9 @@ package chat.simplex.common.platform import androidx.compose.runtime.* -import chat.simplex.common.DesktopApp -import chat.simplex.common.simplexWindowState +import chat.simplex.common.* +import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.res.MR import java.io.* import java.net.URI @@ -31,6 +32,10 @@ actual val databaseExportDir: File = tmpDir actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher = remember { FileChooserLauncher(getContent, onResult) } +@Composable +actual fun rememberFileChooserMultipleLauncher(onResult: (List) -> Unit): FileChooserMultipleLauncher = + remember { FileChooserMultipleLauncher(onResult) } + actual class FileChooserLauncher actual constructor() { var getContent: Boolean = false lateinit var onResult: (URI?) -> Unit @@ -41,10 +46,50 @@ actual class FileChooserLauncher actual constructor() { } actual suspend fun launch(input: String) { - val res = if (getContent) simplexWindowState.openDialog.awaitResult() else simplexWindowState.saveDialog.awaitResult() - onResult(if (!getContent && input.isNotEmpty() && res != null) File(res, input).toURI() else res?.toURI()) + val res = if (getContent) { + val params = DialogParams( + allowMultiple = false, + fileFilter = fileFilter(input), + fileFilterDescription = fileFilterDescription(input), + ) + simplexWindowState.openDialog.awaitResult(params) + } else { + simplexWindowState.saveDialog.awaitResult() + } + onResult(res?.toURI()) } } +actual class FileChooserMultipleLauncher actual constructor() { + lateinit var onResult: (List) -> Unit + + constructor(onResult: (List) -> Unit): this() { + this.onResult = onResult + } + + actual suspend fun launch(input: String) { + val params = DialogParams( + allowMultiple = true, + fileFilter = fileFilter(input), + fileFilterDescription = fileFilterDescription(input), + ) + onResult(simplexWindowState.openMultipleDialog.awaitResult(params).map { it.toURI() }) + } +} + +private fun fileFilter(input: String): (File?) -> Boolean = when(input) { + "image/*" -> { file -> if (file?.isDirectory == true) true else if (file != null) isImage(file.toURI()) else false } + "video/*" -> { file -> if (file?.isDirectory == true) true else if (file != null) isVideo(file.toURI()) else false } + "*/*" -> { _ -> true } + else -> { _ -> true } +} + +private fun fileFilterDescription(input: String): String = when(input) { + "image/*" -> generalGetString(MR.strings.gallery_image_button) + "video/*" -> generalGetString(MR.strings.gallery_video_button) + "*/*" -> generalGetString(MR.strings.choose_file) + else -> "" +} + actual fun URI.inputStream(): InputStream? = File(URI("file:" + toString().removePrefix("file:"))).inputStream() actual fun URI.outputStream(): OutputStream = File(URI("file:" + toString().removePrefix("file:"))).outputStream() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt index 2d6011c223..81699a6cc1 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt @@ -41,7 +41,20 @@ actual fun resizeImageToStrSize(image: ImageBitmap, maxDataSize: Long): String { } return str } -actual fun resizeImageToDataSize(image: ImageBitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream = TODO() +actual fun resizeImageToDataSize(image: ImageBitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream { + var img = image + var stream = compressImageData(img, usePng) + while (stream.size() > maxDataSize) { + val ratio = sqrt(stream.size().toDouble() / maxDataSize.toDouble()) + val clippedRatio = kotlin.math.min(ratio, 2.0) + val width = (img.width.toDouble() / clippedRatio).toInt() + val height = img.height * width / img.width + img = img.scale(width, height) + stream = compressImageData(img, usePng) + } + return stream +} + actual fun cropToSquare(image: ImageBitmap): ImageBitmap { var xOffset = 0 var yOffset = 0 diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Videos.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Videos.desktop.kt new file mode 100644 index 0000000000..54a511e082 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Videos.desktop.kt @@ -0,0 +1,13 @@ +package chat.simplex.common.platform + +import java.net.URI + +fun isVideo(uri: URI): Boolean { + val path = uri.path.lowercase() + return path.endsWith(".mov") || + path.endsWith(".avi") || + path.endsWith(".mp4") || + path.endsWith(".mpg") || + path.endsWith(".mpeg") || + path.endsWith(".mkv") +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt index c7eeef3413..dd9f3dd612 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt @@ -1,7 +1,8 @@ package chat.simplex.common.views.chat import androidx.compose.runtime.* -import chat.simplex.common.views.helpers.AttachmentOption +import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.* import java.net.URI @Composable @@ -11,12 +12,27 @@ actual fun AttachmentSelection( processPickedFile: (URI?, String?) -> Unit, processPickedMedia: (List, String?) -> Unit ) { + val imageLauncher = rememberFileChooserMultipleLauncher { + processPickedMedia(it, null) + } + val videoLauncher = rememberFileChooserMultipleLauncher { + processPickedMedia(it, null) + } + val filesLauncher = rememberFileChooserLauncher(true) { + if (it != null) processPickedFile(it, null) + } LaunchedEffect(attachmentOption.value) { when (attachmentOption.value) { AttachmentOption.CameraPhoto -> {} - AttachmentOption.GalleryImage -> {} - AttachmentOption.GalleryVideo -> {} - AttachmentOption.File -> {} + AttachmentOption.GalleryImage -> { + imageLauncher.launch("image/*") + } + AttachmentOption.GalleryVideo -> { + videoLauncher.launch("video/*") + } + AttachmentOption.File -> { + filesLauncher.launch("*/*") + } else -> {} } attachmentOption.value = null diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.desktop.kt new file mode 100644 index 0000000000..32f5a0a80c --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.desktop.kt @@ -0,0 +1,22 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import chat.simplex.common.views.newchat.ActionButton +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +actual fun ChooseAttachmentButtons(attachmentOption: MutableState, hide: () -> Unit) { + ActionButton(Modifier.fillMaxWidth(0.5f), null, stringResource(MR.strings.gallery_image_button), icon = painterResource(MR.images.ic_add_photo)) { + attachmentOption.value = AttachmentOption.GalleryImage + hide() + } + ActionButton(Modifier.fillMaxWidth(1f), null, stringResource(MR.strings.choose_file), icon = painterResource(MR.images.ic_note_add)) { + attachmentOption.value = AttachmentOption.File + hide() + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt index ade674dff2..58edc0d88b 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt @@ -3,6 +3,8 @@ package chat.simplex.common.views.helpers import androidx.compose.runtime.* import androidx.compose.ui.input.key.* import androidx.compose.ui.window.* +import chat.simplex.common.DialogParams +import chat.simplex.res.MR import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.awt.FileDialog @@ -35,13 +37,13 @@ actual fun DefaultDialog( fun FrameWindowScope.FileDialogChooser( title: String, isLoad: Boolean, - extensions: List = emptyList(), - onResult: (result: File?) -> Unit + params: DialogParams, + onResult: (result: List) -> Unit ) { if (isLinux()) { - FileDialogChooserMultiple(title, isLoad, extensions) { onResult(it.firstOrNull()) } + FileDialogChooserMultiple(title, isLoad, params.allowMultiple, params.fileFilter, params.fileFilterDescription, onResult) } else { - FileDialogAwt(title, isLoad, onResult) + FileDialogAwt(title, isLoad, params.allowMultiple, params.fileFilter, onResult) } } @@ -49,7 +51,9 @@ fun FrameWindowScope.FileDialogChooser( fun FrameWindowScope.FileDialogChooserMultiple( title: String, isLoad: Boolean, - extensions: List = emptyList(), + allowMultiple: Boolean, + fileFilter: ((File?) -> Boolean)? = null, + fileFilterDescription: String? = null, onResult: (result: List) -> Unit ) { val scope = rememberCoroutineScope() @@ -57,9 +61,15 @@ fun FrameWindowScope.FileDialogChooserMultiple( val job = scope.launch(Dispatchers.Main) { val fileChooser = JFileChooser() fileChooser.dialogTitle = title - fileChooser.isMultiSelectionEnabled = isLoad - fileChooser.isAcceptAllFileFilterUsed = extensions.isEmpty() - extensions.forEach { fileChooser.addChoosableFileFilter(it) } + fileChooser.isMultiSelectionEnabled = allowMultiple && isLoad + fileChooser.isAcceptAllFileFilterUsed = fileFilter == null + if (fileFilter != null && fileFilterDescription != null) { + fileChooser.addChoosableFileFilter(object: FileFilter() { + override fun accept(file: File?): Boolean = fileFilter(file) + + override fun getDescription(): String = fileFilterDescription + }) + } val returned = if (isLoad) { fileChooser.showOpenDialog(window) } else { @@ -69,7 +79,11 @@ fun FrameWindowScope.FileDialogChooserMultiple( val result = when (returned) { JFileChooser.APPROVE_OPTION -> { if (isLoad) { - fileChooser.selectedFiles.filter { it.canRead() } + when { + allowMultiple -> fileChooser.selectedFiles.filter { it.canRead() } + fileChooser.selectedFile != null && fileChooser.selectedFile.canRead() -> listOf(fileChooser.selectedFile) + else -> emptyList() + } } else { if (!fileChooser.fileFilter.accept(fileChooser.selectedFile)) { val ext = (fileChooser.fileFilter as FileNameExtensionFilter).extensions[0] @@ -78,7 +92,7 @@ fun FrameWindowScope.FileDialogChooserMultiple( listOf(fileChooser.selectedFile) } } - else -> listOf(); + else -> emptyList() } onResult(result) } @@ -95,22 +109,30 @@ fun FrameWindowScope.FileDialogChooserMultiple( private fun FrameWindowScope.FileDialogAwt( title: String, isLoad: Boolean, - onResult: (result: File?) -> Unit + allowMultiple: Boolean, + fileFilter: ((File?) -> Boolean)? = null, + onResult: (result: List) -> Unit ) = AwtWindow( create = { - object: FileDialog(window, "Choose a file", if (isLoad) LOAD else SAVE) { + object: FileDialog(window, generalGetString(MR.strings.choose_file_title), if (isLoad) LOAD else SAVE) { override fun setVisible(value: Boolean) { super.setVisible(value) if (value) { - if (file != null) { - onResult(File(directory).resolve(file)) + if (files != null) { + onResult(files.toList()) } else { - onResult(null) + onResult(emptyList()) } } } }.apply { this.title = title + this.isMultipleMode = allowMultiple && isLoad + if (fileFilter != null) { + this.setFilenameFilter { dir, file -> + fileFilter(File(dir.absolutePath + File.separator + file)) + } + } } }, dispose = FileDialog::dispose diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt index d824627449..544c15abe8 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt @@ -44,13 +44,8 @@ actual fun GetImageBottomSheet( } } val pickImageLauncher = rememberFileChooserLauncher(true, processPickedImage) - // LALAL - /*ActionButton(null, stringResource(MR.strings.use_camera_button), icon = painterResource(MR.images.ic_photo_camera)) { - hideBottomSheet() - }*/ ActionButton(null, stringResource(MR.strings.from_gallery_button), icon = painterResource(MR.images.ic_image)) { - // LALAL support providing file extensions - withApi { pickImageLauncher.launch("") } + withApi { pickImageLauncher.launch("image/*") } } } }