diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 1fc14e1027..91547c2b87 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -538,7 +538,7 @@ fun BoxWithConstraintsScope.ChatItemsList( ChatItemView(user, chat.chatInfo, cItem, composeState, cxt, uriHandler, provider, showMember = showMember, chatModelIncognito = chatModelIncognito, useLinkPreviews = useLinkPreviews, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, scrollToItem = scrollToItem) } } else { - Box(Modifier.padding(start = 86.dp, end = 12.dp).then(swipeableModifier)) { + Box(Modifier.padding(start = 104.dp, end = 12.dp).then(swipeableModifier)) { ChatItemView(user, chat.chatInfo, cItem, composeState, cxt, uriHandler, provider, chatModelIncognito = chatModelIncognito, useLinkPreviews = useLinkPreviews, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, scrollToItem = scrollToItem) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt index f30719be87..161e7f5945 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt @@ -1,3 +1,5 @@ +package chat.simplex.app.views.chat.item + import android.graphics.Bitmap import android.os.Build.VERSION.SDK_INT import androidx.compose.foundation.Image @@ -9,8 +11,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.outlined.ArrowDownward import androidx.compose.material.icons.outlined.MoreHoriz -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -18,16 +19,16 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.* import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.content.FileProvider -import chat.simplex.app.BuildConfig +import chat.simplex.app.* import chat.simplex.app.R import chat.simplex.app.model.CIFile import chat.simplex.app.model.CIFileStatus -import chat.simplex.app.views.chat.item.ImageFullScreenView -import chat.simplex.app.views.chat.item.ImageGalleryProvider import chat.simplex.app.views.helpers.* import coil.ImageLoader import coil.compose.rememberAsyncImagePainter @@ -93,6 +94,12 @@ fun CIImageView( } } + @Composable + fun imageViewFullWidth(): Dp { + val approximatePadding = 100.dp + return with(LocalDensity.current) { minOf(1000.dp, LocalView.current.width.toDp() - approximatePadding) } + } + @Composable fun imageView(imageBitmap: Bitmap, onClick: () -> Unit) { Image( @@ -101,7 +108,7 @@ fun CIImageView( // .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView // if text is short and take all available width if text is long modifier = Modifier - .width(1000.dp) + .width(if (imageBitmap.width * 0.97 <= imageBitmap.height) imageViewFullWidth() * 0.75f else 1000.dp) .combinedClickable( onLongClick = { showMenu.value = true }, onClick = onClick @@ -118,7 +125,7 @@ fun CIImageView( // .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView // if text is short and take all available width if text is long modifier = Modifier - .width(1000.dp) + .width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else 1000.dp) .combinedClickable( onLongClick = { showMenu.value = true }, onClick = onClick @@ -134,21 +141,20 @@ fun CIImageView( return false } - Box(contentAlignment = Alignment.TopEnd) { + fun imageAndFilePath(file: CIFile?): Pair { + val imageBitmap: Bitmap? = getLoadedImage(SimplexApp.context, file) + val filePath = getLoadedFilePath(SimplexApp.context, file) + return imageBitmap to filePath + } + + Box( + Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID), + contentAlignment = Alignment.TopEnd + ) { val context = LocalContext.current - val imageBitmap: Bitmap? = getLoadedImage(context, file) - val filePath = getLoadedFilePath(context, file) + val (imageBitmap, filePath) = remember(file) { imageAndFilePath(file) } if (imageBitmap != null && filePath != null) { - val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) - val imageLoader = ImageLoader.Builder(context) - .components { - if (SDK_INT >= 28) { - add(ImageDecoderDecoder.Factory()) - } else { - add(GifDecoder.Factory()) - } - } - .build() + val uri = remember(filePath) { FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) } val imagePainter = rememberAsyncImagePainter( ImageRequest.Builder(context).data(data = uri).size(coil.size.Size.ORIGINAL).build(), placeholder = BitmapPainter(imageBitmap.asImageBitmap()), // show original image while it's still loading by coil @@ -190,3 +196,13 @@ fun CIImageView( loadingIndicator() } } + +private val imageLoader = ImageLoader.Builder(SimplexApp.context) + .components { + if (SDK_INT >= 28) { + add(ImageDecoderDecoder.Factory()) + } else { + add(GifDecoder.Factory()) + } + } + .build() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt index 726b4ad37f..61bb6651fb 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt @@ -1,6 +1,5 @@ package chat.simplex.app.views.chat.item -import CIImageView import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -14,13 +13,13 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.* import androidx.compose.ui.platform.UriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.* -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.* +import androidx.compose.ui.util.fastMap import chat.simplex.app.R import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* @@ -119,27 +118,24 @@ fun FramedItemView( var metaColor = HighOrLowlight Box(contentAlignment = Alignment.BottomEnd) { Column(Modifier.width(IntrinsicSize.Max)) { - val qi = ci.quotedItem - if (qi != null) { - ciQuoteView(qi) - } - if (ci.file == null && ci.formattedText == null && isShortEmoji(ci.content.text)) { - Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) { - Column( - Modifier - .padding(bottom = 2.dp) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - EmojiText(ci.content.text) - Text("") + PriorityLayout(Modifier, CHAT_IMAGE_LAYOUT_ID) { + ci.quotedItem?.let { ciQuoteView(it) } + if (ci.file == null && ci.formattedText == null && isShortEmoji(ci.content.text)) { + Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) { + Column( + Modifier + .padding(bottom = 2.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + EmojiText(ci.content.text) + Text("") + } } - } - } else { - Column(Modifier.fillMaxWidth()) { + } else { when (val mc = ci.content.msgContent) { is MsgContent.MCImage -> { - CIImageView(image = mc.image, file = ci.file, imageProvider ?: return@Box, showMenu, receiveFile) + CIImageView(image = mc.image, file = ci.file, imageProvider ?: return@PriorityLayout, showMenu, receiveFile) if (mc.text == "") { metaColor = Color.White } else { @@ -184,6 +180,37 @@ fun CIMarkdownText( } } +const val CHAT_IMAGE_LAYOUT_ID = "chatImage" + +@Composable +fun PriorityLayout( + modifier: Modifier = Modifier, + priorityLayoutId: String, + content: @Composable () -> Unit +) { + Layout( + content = content, + modifier = modifier + ) { measureable, constraints -> + // Find important element which should tell what max width other elements can use + // Expecting only one such element. Can be less than one but not more + val imagePlaceable = measureable.firstOrNull { it.layoutId == priorityLayoutId }?.measure(constraints) + val placeables: List = measureable.fastMap { + if (it.layoutId == priorityLayoutId) + imagePlaceable!! + else + it.measure(constraints.copy(maxWidth = imagePlaceable?.width ?: constraints.maxWidth)) } + // Limit width for every other element to width of important element and height for a sum of all elements + layout(imagePlaceable?.measuredWidth ?: placeables.maxOf { it.width }, placeables.sumOf { it.height }) { + var y = 0 + placeables.forEach { + it.place(0, y) + y += it.measuredHeight + } + } + } +} + class EditedProvider: PreviewParameterProvider { override val values = listOf(false, true).asSequence() } diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml index 1ea4dc4da8..14904a09b9 100644 --- a/apps/android/app/src/main/res/values-de/strings.xml +++ b/apps/android/app/src/main/res/values-de/strings.xml @@ -183,7 +183,7 @@ Zu viele Bilder! Es können nur 10 Bilder auf einmal gesendet werden - + Bild Warten auf ein Bild Um Empfang des Bildes gebeten diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index 27c260630f..5ed02be26b 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -183,7 +183,7 @@ Слишком много изображений! Только 10 изображений могут быть отправлены одномоментно - + Изображение Ожидается прием изображения Предложено получить изображение diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index d7db67af3e..9be629ca63 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -183,7 +183,7 @@ Too many images! Only 10 images can be sent at the same time - + Image Waiting for image Asked to receive the image