From 93a7ddb128e8252a874e4b61aa3bcabc7881fe0d Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 9 May 2024 16:39:49 +0700 Subject: [PATCH] android, desktop: fix presenting large images (#4139) * android, desktop: fix presenting large images * 4320 px limitation * revert line * onClick * onClick --- .../simplex/common/platform/Share.android.kt | 23 ++++++-- .../chat/simplex/common/platform/Share.kt | 1 + .../common/views/chat/item/CIFileView.kt | 3 +- .../common/views/chat/item/CIImageView.kt | 55 ++++++++++++++----- .../simplex/common/platform/Share.desktop.kt | 28 ++++++++++ .../views/chat/item/ChatItemView.desktop.kt | 15 +---- 6 files changed, 90 insertions(+), 35 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt index ad0d914ea8..385e6c82a4 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt @@ -37,7 +37,7 @@ actual fun ClipboardManager.shareText(text: String) { } } -actual fun shareFile(text: String, fileSource: CryptoFile) { +fun openOrShareFile(text: String, fileSource: CryptoFile, justOpen: Boolean) { val uri = if (fileSource.cryptoArgs != null) { val tmpFile = File(tmpDir, fileSource.filePath) tmpFile.deleteOnExit() @@ -55,20 +55,31 @@ actual fun shareFile(text: String, fileSource: CryptoFile) { } val ext = fileSource.filePath.substringAfterLast(".") val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) ?: return - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND + val sendIntent: Intent = Intent(if (justOpen) Intent.ACTION_VIEW else Intent.ACTION_SEND).apply { /*if (text.isNotEmpty()) { putExtra(Intent.EXTRA_TEXT, text) }*/ - putExtra(Intent.EXTRA_STREAM, uri.toUri()) - type = mimeType - flags = Intent.FLAG_ACTIVITY_NEW_TASK + if (justOpen) { + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + setDataAndType(uri.toUri(), mimeType) + } else { + putExtra(Intent.EXTRA_STREAM, uri.toUri()) + type = mimeType + } } val shareIntent = Intent.createChooser(sendIntent, null) shareIntent.addFlags(FLAG_ACTIVITY_NEW_TASK) androidAppContext.startActivity(shareIntent) } +actual fun shareFile(text: String, fileSource: CryptoFile) { + openOrShareFile(text, fileSource, justOpen = false) +} + +actual fun openFile(fileSource: CryptoFile) { + openOrShareFile("", fileSource, justOpen = true) +} + actual fun UriHandler.sendEmail(subject: String, body: CharSequence) { val emailIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")) emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Share.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Share.kt index 72bb3caaac..bd469298b7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Share.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Share.kt @@ -8,3 +8,4 @@ expect fun UriHandler.sendEmail(subject: String, body: CharSequence) expect fun ClipboardManager.shareText(text: String) expect fun shareFile(text: String, fileSource: CryptoFile) +expect fun openFile(fileSource: CryptoFile) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt index dbed57d5fc..6ad75057f6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt @@ -181,7 +181,8 @@ fun CIFileView( } Row( - Modifier.padding(top = 4.dp, bottom = 6.dp, start = 6.dp, end = 12.dp), + Modifier.clickable(onClick = { fileAction() }).padding(top = 4.dp, bottom = 6.dp, start = 6.dp, end = 12.dp), + //Modifier.clickable(enabled = file?.fileSource != null) { if (file?.fileSource != null && getLoadedFilePath(file) != null) openFile(file.fileSource) }.padding(top = 4.dp, bottom = 6.dp, start = 6.dp, end = 12.dp), verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.spacedBy(2.dp) ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt index 5aed7742bc..65fb38575d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt @@ -2,8 +2,7 @@ package chat.simplex.common.views.chat.item import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Icon +import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -113,21 +112,49 @@ fun CIImageView( } @Composable - fun ImageView(painter: Painter, onClick: () -> Unit) { - Image( - painter, - contentDescription = stringResource(MR.strings.image_descr), - // .width(DEFAULT_MAX_IMAGE_WIDTH) 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 + fun ImageView(painter: Painter, image: String, fileSource: CryptoFile?, onClick: () -> Unit) { + // On my Android device Compose fails to display 6000x6000 px WebP image with exception: + // IllegalStateException: Recording currently in progress - missing #endRecording() call? + // but can display 5000px image. Using even lower value here just to feel safer. + // It happens to WebP because it's not compressed while sending since it can be animated. + if (painter.intrinsicSize.width <= 4320 && painter.intrinsicSize.height <= 4320) { + Image( + painter, + contentDescription = stringResource(MR.strings.image_descr), + // .width(DEFAULT_MAX_IMAGE_WIDTH) 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(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else DEFAULT_MAX_IMAGE_WIDTH) + .combinedClickable( + onLongClick = { showMenu.value = true }, + onClick = onClick + ) + .onRightClick { showMenu.value = true }, + contentScale = ContentScale.FillWidth, + ) + } else { + Box(Modifier .width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else DEFAULT_MAX_IMAGE_WIDTH) .combinedClickable( onLongClick = { showMenu.value = true }, - onClick = onClick + onClick = {} ) .onRightClick { showMenu.value = true }, - contentScale = ContentScale.FillWidth, - ) + contentAlignment = Alignment.Center + ) { + imageView(base64ToBitmap(image), onClick = { + if (fileSource != null) { + openFile(fileSource) + } + }) + Icon( + painterResource(MR.images.ic_open_in_new), + contentDescription = stringResource(MR.strings.image_descr), + modifier = Modifier.size(30.dp), + tint = MaterialTheme.colors.primary, + ) + } + } } fun fileSizeValid(): Boolean { @@ -172,9 +199,9 @@ fun CIImageView( } } val loaded = res.value - if (loaded != null) { + if (loaded != null && file != null) { val (imageBitmap, data, _) = loaded - SimpleAndAnimatedImageView(data, imageBitmap, file, imageProvider, @Composable { painter, onClick -> ImageView(painter, onClick) }) + SimpleAndAnimatedImageView(data, imageBitmap, file, imageProvider, @Composable { painter, onClick -> ImageView(painter, image, file.fileSource, onClick) }) } else { imageView(base64ToBitmap(image), onClick = { if (file != null) { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt index 5817275a5f..a10f675085 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt @@ -9,6 +9,7 @@ import java.io.File import java.net.URI import java.net.URLEncoder import chat.simplex.res.MR +import java.awt.Desktop actual fun UriHandler.sendEmail(subject: String, body: CharSequence) { val subjectEncoded = URLEncoder.encode(subject, "UTF-8").replace("+", "%20") @@ -41,3 +42,30 @@ actual fun shareFile(text: String, fileSource: CryptoFile) { }.launch(fileSource.filePath) } } + +actual fun openFile(fileSource: CryptoFile) { + try { + val filePath = filePathForShare(fileSource) ?: return + Desktop.getDesktop().open(File(filePath)) + } catch (e: Exception) { + Log.e(TAG, "Unable to open the file: " + e.stackTraceToString()) + AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.error), text = e.stackTraceToString()) + } +} + +fun filePathForShare(fileSource: CryptoFile): String? { + return if (fileSource.cryptoArgs != null) { + val tmpFile = File(tmpDir, fileSource.filePath) + tmpFile.deleteOnExit() + try { + decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs ?: return null, tmpFile.absolutePath) + } catch (e: Exception) { + Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString()) + AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.error), text = e.stackTraceToString()) + return null + } + tmpFile.absolutePath + } else { + getAppFilePath(fileSource.filePath) + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt index 9b2911350f..cd206c8e4e 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt @@ -59,20 +59,7 @@ actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) = w } if (fileSource != null) { - val filePath: String = if (fileSource.cryptoArgs != null) { - val tmpFile = File(tmpDir, fileSource.filePath) - tmpFile.deleteOnExit() - try { - decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs ?: return@withLongRunningApi, tmpFile.absolutePath) - } catch (e: Exception) { - Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString()) - AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.error), text = e.stackTraceToString()) - return@withLongRunningApi - } - tmpFile.absolutePath - } else { - getAppFilePath(fileSource.filePath) - } + val filePath = filePathForShare(fileSource) ?: return@withLongRunningApi when { desktopPlatform.isWindows() -> clipboard.setText(AnnotatedString("\"${File(filePath).absolutePath}\"")) else -> clipboard.setText(AnnotatedString(filePath))