mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-06 23:39:54 +00: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:
co-authored by
Evgeny Poberezkin
parent
78510b6fd3
commit
24090fe350
+1
-1
@@ -21,7 +21,7 @@ actual fun ProvideWindowInsets(
|
||||
actual fun Modifier.desktopOnExternalDrag(
|
||||
enabled: Boolean,
|
||||
onFiles: (List<File>) -> Unit,
|
||||
onImage: (Painter) -> Unit,
|
||||
onImage: (File) -> Unit,
|
||||
onText: (String) -> Unit
|
||||
): Modifier = this
|
||||
|
||||
|
||||
+8
-3
@@ -26,6 +26,7 @@ import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.inputmethod.EditorInfoCompat
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
@@ -94,8 +95,8 @@ actual fun PlatformTextField(
|
||||
}
|
||||
|
||||
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||
AndroidView(modifier = Modifier, factory = {
|
||||
val editText = @SuppressLint("AppCompatCustomView") object: EditText(it) {
|
||||
AndroidView(modifier = Modifier, factory = { context ->
|
||||
val editText = @SuppressLint("AppCompatCustomView") object: EditText(context) {
|
||||
override fun setOnReceiveContentListener(
|
||||
mimeTypes: Array<out String>?,
|
||||
listener: OnReceiveContentListener?
|
||||
@@ -148,8 +149,12 @@ actual fun PlatformTextField(
|
||||
}
|
||||
}
|
||||
editText.doAfterTextChanged { text -> if (composeState.value.preview is ComposePreview.VoicePreview && text.toString() != "") editText.setText("") }
|
||||
editText
|
||||
val workaround = WorkaroundFocusSearchLayout(context)
|
||||
workaround.addView(editText)
|
||||
workaround.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
workaround
|
||||
}) {
|
||||
val it = it.children.first() as EditText
|
||||
it.setTextColor(textColor.toArgb())
|
||||
it.setHintTextColor(hintColor.toArgb())
|
||||
it.hint = placeholder
|
||||
|
||||
+2
-2
@@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
@@ -411,6 +410,7 @@ private fun ControlButton(icon: Painter, iconText: StringResource, enabled: Bool
|
||||
|
||||
@Composable
|
||||
private fun ControlButtonWrap(enabled: Boolean = true, action: () -> Unit, background: Color = controlButtonsBackground(), size: Dp, content: @Composable () -> Unit) {
|
||||
val ripple = remember { ripple(bounded = false, radius = size / 2, color = background.lighter(0.1f)) }
|
||||
Box(
|
||||
Modifier
|
||||
.background(background, CircleShape)
|
||||
@@ -419,7 +419,7 @@ private fun ControlButtonWrap(enabled: Boolean = true, action: () -> Unit, backg
|
||||
onClick = action,
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false, radius = size / 2, color = background.lighter(0.1f)),
|
||||
indication = ripple,
|
||||
enabled = enabled
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
|
||||
+3
-4
@@ -6,7 +6,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -40,8 +39,8 @@ actual fun ActiveCallInteractiveArea(call: Call) {
|
||||
val onClick = { platform.androidStartCallActivity(false) }
|
||||
Box(Modifier.offset(y = CALL_TOP_OFFSET).height(CALL_INTERACTIVE_AREA_HEIGHT)) {
|
||||
val source = remember { MutableInteractionSource() }
|
||||
val indication = rememberRipple(bounded = true, 3000.dp)
|
||||
Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT).clickable(onClick = onClick, indication = indication, interactionSource = source)) {
|
||||
val ripple = remember { ripple(bounded = true, 3000.dp) }
|
||||
Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT).clickable(onClick = onClick, indication = ripple, interactionSource = source)) {
|
||||
GreenLine(call)
|
||||
}
|
||||
Box(
|
||||
@@ -50,7 +49,7 @@ actual fun ActiveCallInteractiveArea(call: Call) {
|
||||
.size(CALL_BOTTOM_ICON_HEIGHT)
|
||||
.background(SimplexGreen, CircleShape)
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = onClick, indication = indication, interactionSource = source)
|
||||
.clickable(onClick = onClick, indication = ripple, interactionSource = source)
|
||||
.align(Alignment.BottomCenter),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package chat.simplex.common.views.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
|
||||
/**
|
||||
* A workaround for the ANR issue on Compose 1.7.x.
|
||||
* https://issuetracker.google.com/issues/369354336
|
||||
* Code from:
|
||||
* https://issuetracker.google.com/issues/369354336#comment8
|
||||
*/
|
||||
class WorkaroundFocusSearchLayout : FrameLayout {
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
) : super(context)
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
) : super(context, attrs)
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
defStyleAttr: Int,
|
||||
) : super(context, attrs, defStyleAttr)
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
defStyleAttr: Int,
|
||||
defStyleRes: Int,
|
||||
) : super(context, attrs, defStyleAttr, defStyleRes)
|
||||
|
||||
override fun focusSearch(focused: View?, direction: Int): View? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@ expect fun ProvideWindowInsets(
|
||||
expect fun Modifier.desktopOnExternalDrag(
|
||||
enabled: Boolean = true,
|
||||
onFiles: (List<File>) -> Unit = {},
|
||||
onImage: (Painter) -> Unit = {},
|
||||
onImage: (File) -> Unit = {},
|
||||
onText: (String) -> Unit = {}
|
||||
): Modifier
|
||||
|
||||
|
||||
+1
@@ -289,6 +289,7 @@ fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isVal
|
||||
enabled = true,
|
||||
isError = false,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
+1
-8
@@ -642,14 +642,7 @@ fun ChatLayout(
|
||||
.desktopOnExternalDrag(
|
||||
enabled = remember(attachmentDisabled.value, chatInfo.value?.userCanSend) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.userCanSend == true) }.value,
|
||||
onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) },
|
||||
onImage = {
|
||||
// TODO: file is not saved anywhere?!
|
||||
val tmpFile = File.createTempFile("image", ".bmp", tmpDir)
|
||||
tmpFile.deleteOnExit()
|
||||
chatModel.filesToDelete.add(tmpFile)
|
||||
val uri = tmpFile.toURI()
|
||||
CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(uri), null) }
|
||||
},
|
||||
onImage = { file -> CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(file.toURI()), null) } },
|
||||
onText = {
|
||||
// Need to parse HTML in order to correctly display the content
|
||||
//composeState.value = composeState.value.copy(message = composeState.value.message + it)
|
||||
|
||||
+4
-3
@@ -7,7 +7,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.*
|
||||
@@ -423,6 +422,7 @@ private fun SendMsgButton(
|
||||
onLongClick: (() -> Unit)? = null
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val ripple = remember { ripple(bounded = false, radius = 24.dp) }
|
||||
Box(
|
||||
modifier = Modifier.requiredSize(36.dp)
|
||||
.combinedClickable(
|
||||
@@ -431,7 +431,7 @@ private fun SendMsgButton(
|
||||
enabled = enabled,
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = rememberRipple(bounded = false, radius = 24.dp)
|
||||
indication = ripple
|
||||
)
|
||||
.onRightClick { onLongClick?.invoke() },
|
||||
contentAlignment = Alignment.Center
|
||||
@@ -454,6 +454,7 @@ private fun SendMsgButton(
|
||||
@Composable
|
||||
private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val ripple = remember { ripple(bounded = false, radius = 24.dp) }
|
||||
Box(
|
||||
modifier = Modifier.requiredSize(36.dp)
|
||||
.clickable(
|
||||
@@ -461,7 +462,7 @@ private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
|
||||
enabled = enabled,
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = rememberRipple(bounded = false, radius = 24.dp)
|
||||
indication = ripple
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
|
||||
-3
@@ -3,7 +3,6 @@ package chat.simplex.common.views.helpers
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||
import androidx.compose.foundation.text.*
|
||||
import androidx.compose.material.*
|
||||
@@ -22,13 +21,11 @@ import androidx.compose.ui.text.input.*
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.views.database.PassphraseStrength
|
||||
import chat.simplex.common.views.database.validKey
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun DefaultBasicTextField(
|
||||
modifier: Modifier,
|
||||
|
||||
+2
-2
@@ -6,7 +6,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -107,6 +106,7 @@ fun <T> ExposedDropDownSettingWithIcon(
|
||||
expanded.value = !expanded.value && enabled.value
|
||||
}
|
||||
) {
|
||||
val ripple = remember { ripple(bounded = false, radius = boxSize / 2, color = background.lighter(0.1f)) }
|
||||
Box(
|
||||
Modifier
|
||||
.background(background, CircleShape)
|
||||
@@ -115,7 +115,7 @@ fun <T> ExposedDropDownSettingWithIcon(
|
||||
onClick = {},
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false, radius = boxSize / 2, color = background.lighter(0.1f)),
|
||||
indication = ripple,
|
||||
enabled = enabled.value
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
|
||||
+1
@@ -87,6 +87,7 @@ fun TextEditor(
|
||||
enabled = true,
|
||||
isError = false,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
+1
@@ -616,6 +616,7 @@ fun LinkTextView(link: String, share: Boolean) {
|
||||
enabled = false,
|
||||
isError = false,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
+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()
|
||||
|
||||
@@ -21,8 +21,6 @@ kotlin.code.style=official
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
kotlin.jvm.target=11
|
||||
|
||||
@@ -34,4 +32,4 @@ desktop.version_code=74
|
||||
|
||||
kotlin.version=1.9.23
|
||||
gradle.plugin.version=8.2.0
|
||||
compose.version=1.6.1
|
||||
compose.version=1.7.0
|
||||
|
||||
Reference in New Issue
Block a user