mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
android, desktop: update to Compose 1.7.0 (#5038)
* docs: correction * android, desktop: update to Compose 1.7.0 - support image drag-and-drop from other applications right to a chat (with and without transparent pixels - will be png or jpg) * stable * workaround --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
committed by
GitHub
parent
78510b6fd3
commit
24090fe350
+32
@@ -206,3 +206,35 @@ fun BufferedImage.flip(vertically: Boolean, horizontally: Boolean): BufferedImag
|
||||
}
|
||||
return AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(this, null)
|
||||
}
|
||||
|
||||
fun BufferedImage.saveInTmpFile(): File? {
|
||||
val formats = arrayOf("jpg", "png")
|
||||
for (format in formats) {
|
||||
val tmpFile = File.createTempFile("image", ".$format", tmpDir)
|
||||
try {
|
||||
// May fail on JPG, using PNG as an alternative
|
||||
val success = ImageIO.write(this, format, tmpFile)
|
||||
if (success) {
|
||||
tmpFile.deleteOnExit()
|
||||
chatModel.filesToDelete.add(tmpFile)
|
||||
return tmpFile
|
||||
} else {
|
||||
tmpFile.delete()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, e.stackTraceToString())
|
||||
tmpFile.delete()
|
||||
return null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun BufferedImage.hasAlpha(): Boolean {
|
||||
for (x in 0 until width) {
|
||||
for (y in 0 until height) {
|
||||
if (getRGB(x, y) == 0) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+62
-10
@@ -1,10 +1,17 @@
|
||||
package chat.simplex.common.platform
|
||||
|
||||
import androidx.compose.foundation.contextMenuOpenDetector
|
||||
import androidx.compose.foundation.draganddrop.dragAndDropTarget
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.*
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.draganddrop.*
|
||||
import androidx.compose.ui.draganddrop.DragData
|
||||
import androidx.compose.ui.input.pointer.*
|
||||
import java.awt.Image
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
import java.awt.datatransfer.Transferable
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
|
||||
@@ -23,16 +30,61 @@ actual fun ProvideWindowInsets(
|
||||
actual fun Modifier.desktopOnExternalDrag(
|
||||
enabled: Boolean,
|
||||
onFiles: (List<File>) -> Unit,
|
||||
onImage: (Painter) -> Unit,
|
||||
onImage: (File) -> Unit,
|
||||
onText: (String) -> Unit
|
||||
): Modifier =
|
||||
onExternalDrag(enabled) {
|
||||
when(val data = it.dragData) {
|
||||
// data.readFiles() returns filePath in URI format (where spaces replaces with %20). But it's an error-prone idea to work later
|
||||
// with such format when everywhere we use absolutePath in File() format
|
||||
is DragData.FilesList -> onFiles(data.readFiles().map { URI.create(it).toFile() })
|
||||
is DragData.Image -> onImage(data.readImage())
|
||||
is DragData.Text -> onText(data.readText())
|
||||
): Modifier {
|
||||
val callback = remember {
|
||||
object : DragAndDropTarget {
|
||||
override fun onDrop(event: DragAndDropEvent): Boolean {
|
||||
when (val data = event.dragData()) {
|
||||
// data.readFiles() returns filePath in URI format (where spaces replaces with %20). But it's an error-prone idea to work later
|
||||
// with such format when everywhere we use absolutePath in File() format
|
||||
is DragData.FilesList -> {
|
||||
val files = data.readFiles()
|
||||
// When dragging and dropping an image from browser, it comes to FilesList section but no files inside
|
||||
if (files.isNotEmpty()) {
|
||||
onFiles(files.map { URI.create(it).toFile() })
|
||||
} else {
|
||||
try {
|
||||
val transferable = event.awtTransferable
|
||||
if (transferable.isDataFlavorSupported(DataFlavor.imageFlavor)) {
|
||||
onImage(DragDataImageImpl(transferable).bufferedImage().saveInTmpFile() ?: return false)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, e.stackTraceToString())
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
is DragData.Image -> onImage(DragDataImageImpl(event.awtTransferable).bufferedImage().saveInTmpFile() ?: return false)
|
||||
is DragData.Text -> onText(data.readText())
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return dragAndDropTarget(shouldStartDragAndDrop = { true }, target = callback)
|
||||
}
|
||||
|
||||
// Copied from AwtDragData and modified
|
||||
private class DragDataImageImpl(private val transferable: Transferable) {
|
||||
fun bufferedImage(): BufferedImage = (transferable.getTransferData(DataFlavor.imageFlavor) as Image).bufferedImage()
|
||||
private fun Image.bufferedImage(): BufferedImage {
|
||||
if (this is BufferedImage && hasAlpha()) {
|
||||
// Such image cannot be drawn as JPG, only PNG
|
||||
return this
|
||||
}
|
||||
// Creating non-transparent image which can be drawn as JPG
|
||||
val bufferedImage = BufferedImage(getWidth(null), getHeight(null), BufferedImage.TYPE_INT_RGB)
|
||||
val g2 = bufferedImage.createGraphics()
|
||||
try {
|
||||
g2.drawImage(this, 0, 0, null)
|
||||
} finally {
|
||||
g2.dispose()
|
||||
}
|
||||
return bufferedImage
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -10,6 +10,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.input.key.*
|
||||
import androidx.compose.ui.platform.*
|
||||
@@ -109,7 +110,7 @@ actual fun PlatformTextField(
|
||||
maxLines = 16,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
autoCorrect = true
|
||||
autoCorrectEnabled = true
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(vertical = 4.dp)
|
||||
@@ -193,6 +194,7 @@ actual fun PlatformTextField(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
contentPadding = PaddingValues(),
|
||||
visualTransformation = VisualTransformation.None,
|
||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified)
|
||||
)
|
||||
Spacer(Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
+9
-9
@@ -10,20 +10,20 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||
import androidx.compose.ui.node.DelegatableNode
|
||||
import androidx.compose.ui.node.DrawModifierNode
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.platform.onRightClick
|
||||
import chat.simplex.common.views.helpers.*
|
||||
|
||||
object NoIndication : Indication {
|
||||
private object NoIndicationInstance : IndicationInstance {
|
||||
override fun ContentDrawScope.drawIndication() {
|
||||
drawContent()
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
|
||||
return NoIndicationInstance
|
||||
object NoIndication : IndicationNodeFactory {
|
||||
// Should be as a class, not an object. Otherwise, crash
|
||||
private class NoIndicationInstance : Modifier.Node(), DrawModifierNode {
|
||||
override fun ContentDrawScope.draw() { drawContent() }
|
||||
}
|
||||
override fun create(interactionSource: InteractionSource): DelegatableNode = NoIndicationInstance()
|
||||
override fun hashCode(): Int = -1
|
||||
override fun equals(other: Any?) = other === this
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
+3
-1
@@ -156,7 +156,9 @@ actual fun getFileSize(uri: URI): Long? = uri.toFile().length()
|
||||
|
||||
actual fun getBitmapFromUri(uri: URI, withAlertOnException: Boolean): ImageBitmap? =
|
||||
try {
|
||||
ImageIO.read(uri.inputStream()).toComposeImageBitmap()
|
||||
uri.inputStream().use {
|
||||
ImageIO.read(it).toComposeImageBitmap()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error while decoding drawable: ${e.stackTraceToString()}")
|
||||
if (withAlertOnException) showImageDecodingException()
|
||||
|
||||
Reference in New Issue
Block a user