From f50e511da028fbd1aa5f7192ab8941de5b819e4d Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Sun, 30 Jun 2024 10:20:14 -0300 Subject: [PATCH 1/7] Cut the text when the content length is greater than SHORT_TEXT_LENGTH and theres no spaces and new lines --- .../commons/richtext/ExpandableTextCutOffCalculator.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/ExpandableTextCutOffCalculator.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/ExpandableTextCutOffCalculator.kt index 8cabd98fd..9a91c8ae5 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/ExpandableTextCutOffCalculator.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/ExpandableTextCutOffCalculator.kt @@ -24,7 +24,7 @@ class ExpandableTextCutOffCalculator { companion object { private const val SHORT_TEXT_LENGTH = 350 private const val SHORTEN_AFTER_LINES = 10 - private const val TOO_FAR_SEACH_THE_OTHER_WAY = 450 + private const val TOO_FAR_SEARCH_THE_OTHER_WAY = 450 fun indexToCutOff(content: String): Int { // Cuts the text in the first space or new line after SHORT_TEXT_LENGTH characters @@ -37,12 +37,15 @@ class ExpandableTextCutOffCalculator { val min = minOf(firstSpaceAfterCut, firstNewLineAfterCut, firstLineAfterLineLimits) - if (min > TOO_FAR_SEACH_THE_OTHER_WAY) { + if (min > TOO_FAR_SEARCH_THE_OTHER_WAY) { val newString = content.take(SHORT_TEXT_LENGTH) val firstSpaceBeforeCut = newString.lastIndexOf(' ').let { if (it < 0) content.length else it } val firstNewLineBeforeCut = newString.lastIndexOf('\n').let { if (it < 0) content.length else it } + if (maxOf(firstSpaceBeforeCut, firstNewLineBeforeCut) == content.length && content.length > SHORT_TEXT_LENGTH) { + return SHORT_TEXT_LENGTH + } return maxOf(firstSpaceBeforeCut, firstNewLineBeforeCut) } else { From 3a7b503890fe87e87098d7b956585174f4fd40b4 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 1 Jul 2024 14:25:29 -0300 Subject: [PATCH 2/7] Show base64 images --- .../amethyst/ui/components/RichTextViewer.kt | 51 +++++++++++++++++++ .../ExpandableTextCutOffCalculator.kt | 3 -- .../commons/richtext/RichTextParser.kt | 26 +++++++++- .../richtext/RichTextParserSegments.kt | 4 ++ 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 66daf83e2..b86990d5c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -20,6 +20,8 @@ */ package com.vitorpamplona.amethyst.ui.components +import android.util.Base64 +import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -28,7 +30,10 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent import androidx.compose.material3.Icon @@ -47,6 +52,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFontFamilyResolver import androidx.compose.ui.platform.LocalLayoutDirection @@ -61,7 +68,12 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.em import androidx.lifecycle.viewmodel.compose.viewModel +import coil.compose.AsyncImagePainter +import coil.compose.SubcomposeAsyncImage +import coil.compose.SubcomposeAsyncImageContent +import coil.request.ImageRequest import com.vitorpamplona.amethyst.commons.compose.produceCachedState +import com.vitorpamplona.amethyst.commons.richtext.Base64Segment import com.vitorpamplona.amethyst.commons.richtext.BechSegment import com.vitorpamplona.amethyst.commons.richtext.CashuSegment import com.vitorpamplona.amethyst.commons.richtext.EmailSegment @@ -74,6 +86,7 @@ import com.vitorpamplona.amethyst.commons.richtext.InvoiceSegment import com.vitorpamplona.amethyst.commons.richtext.LinkSegment import com.vitorpamplona.amethyst.commons.richtext.PhoneSegment import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment +import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState import com.vitorpamplona.amethyst.commons.richtext.SchemelessUrlSegment import com.vitorpamplona.amethyst.commons.richtext.Segment @@ -86,6 +99,7 @@ import com.vitorpamplona.amethyst.model.checkForHashtagWithIcon import com.vitorpamplona.amethyst.service.CachedRichTextParser import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.markdown.RenderContentAsMarkdown +import com.vitorpamplona.amethyst.ui.note.BlankNote import com.vitorpamplona.amethyst.ui.note.LoadUser import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.toShortenHex @@ -433,6 +447,43 @@ private fun RenderWordWithPreview( is HashIndexEventSegment -> TagLink(word, true, quotesLeft, backgroundColor, accountViewModel, nav) is SchemelessUrlSegment -> NoProtocolUrlRenderer(word) is RegularTextSegment -> Text(word.segmentText) + is Base64Segment -> ImageFromBase64(word.segmentText) + } +} + +@Composable +fun ImageFromBase64(base64String: String) { + val context = LocalContext.current + + var base64String2 = base64String.removePrefix("data:image/jpeg;base64,") + RichTextParser.imageExtensions.forEach { + base64String2 = base64String2.removePrefix("data:image/$it;base64,") + } + + val imageBytes = runCatching { Base64.decode(base64String2, Base64.DEFAULT) }.getOrNull() + + if (imageBytes == null) { + BlankNote() + } else { + val request = + ImageRequest.Builder(context) + .data(imageBytes) + .build() + + SubcomposeAsyncImage( + model = request, + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier.fillMaxWidth(), + ) { + when (painter.state) { + is AsyncImagePainter.State.Success -> { + SubcomposeAsyncImageContent() + } + + else -> BlankNote() + } + } } } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/ExpandableTextCutOffCalculator.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/ExpandableTextCutOffCalculator.kt index 9a91c8ae5..e537ce840 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/ExpandableTextCutOffCalculator.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/ExpandableTextCutOffCalculator.kt @@ -43,9 +43,6 @@ class ExpandableTextCutOffCalculator { newString.lastIndexOf(' ').let { if (it < 0) content.length else it } val firstNewLineBeforeCut = newString.lastIndexOf('\n').let { if (it < 0) content.length else it } - if (maxOf(firstSpaceBeforeCut, firstNewLineBeforeCut) == content.length && content.length > SHORT_TEXT_LENGTH) { - return SHORT_TEXT_LENGTH - } return maxOf(firstSpaceBeforeCut, firstNewLineBeforeCut) } else { diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt index 226263367..44e51df8f 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt @@ -81,6 +81,21 @@ class RichTextParser { } } + private fun parseBase64Images(content: String): LinkedHashSet { + val regex = "data:image/(${imageExtensions.joinToString(separator = "|") { it } });base64,[a-zA-Z0-9+/]+={0,2}" + val pattern = Pattern.compile(regex) + val matcher = pattern.matcher(content) + + val base64Images = mutableListOf() + + // Find all matches and add them to the list + while (matcher.find()) { + base64Images.add(matcher.group()) + } + + return base64Images.mapTo(LinkedHashSet(base64Images.size)) { it } + } + fun parseValidUrls(content: String): LinkedHashSet { val urls = UrlDetector(content, UrlDetectorOptions.Default).detect() @@ -112,13 +127,15 @@ class RichTextParser { ): RichTextViewerState { val urlSet = parseValidUrls(content) + val base64Images = parseBase64Images(content) + val imagesForPager = urlSet.mapNotNull { fullUrl -> parseMediaUrl(fullUrl, tags, content, callbackUri) }.associateBy { it.url } val imageList = imagesForPager.values.toList() val emojiMap = Nip30CustomEmoji.createEmojiMap(tags) - val segments = findTextSegments(content, imagesForPager.keys, urlSet, emojiMap, tags) + val segments = findTextSegments(content, imagesForPager.keys, urlSet, emojiMap, tags, base64Images) return RichTextViewerState( urlSet.toImmutableSet(), @@ -126,6 +143,7 @@ class RichTextParser { imageList.toImmutableList(), emojiMap.toImmutableMap(), segments, + base64Images.toImmutableSet(), ) } @@ -135,6 +153,7 @@ class RichTextParser { urls: Set, emojis: Map, tags: ImmutableListOfLists, + base64Images: Set, ): ImmutableList { val lines = content.split('\n') val paragraphSegments = ArrayList(lines.size) @@ -146,7 +165,7 @@ class RichTextParser { val wordList = paragraph.trimEnd().split(' ') val segments = ArrayList(wordList.size) wordList.forEach { word -> - val wordSegment = wordIdentifier(word, images, urls, emojis, tags) + val wordSegment = wordIdentifier(word, images, urls, emojis, tags, base64Images) if (wordSegment !is RegularTextSegment) { isDirty = true } @@ -200,9 +219,12 @@ class RichTextParser { urls: Set, emojis: Map, tags: ImmutableListOfLists, + base64Images: Set, ): Segment { if (word.isEmpty()) return RegularTextSegment(word) + if (base64Images.contains(word)) return Base64Segment(word) + if (images.contains(word)) return ImageSegment(word) if (urls.contains(word)) return LinkSegment(word) diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt index 4937c1f40..e4a38683f 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt @@ -32,6 +32,7 @@ data class RichTextViewerState( val imageList: ImmutableList, val customEmoji: ImmutableMap, val paragraphs: ImmutableList, + val base64Images: ImmutableSet, ) @Immutable @@ -67,6 +68,9 @@ class PhoneSegment(segment: String) : Segment(segment) @Immutable class BechSegment(segment: String) : Segment(segment) +@Immutable +class Base64Segment(segment: String) : Segment(segment) + @Immutable open class HashIndexSegment(segment: String, val hex: String, val extras: String?) : Segment(segment) From 2bd05d80b026d35486cefbce191e99ee2beb162f Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 1 Jul 2024 14:50:19 -0300 Subject: [PATCH 3/7] Use Base64Requester --- .../amethyst/ui/components/RichTextViewer.kt | 41 +++++-------------- .../ui/components/RobohashAsyncImage.kt | 6 ++- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index b86990d5c..832a68f95 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.components -import android.util.Base64 import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -71,7 +70,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImagePainter import coil.compose.SubcomposeAsyncImage import coil.compose.SubcomposeAsyncImageContent -import coil.request.ImageRequest import com.vitorpamplona.amethyst.commons.compose.produceCachedState import com.vitorpamplona.amethyst.commons.richtext.Base64Segment import com.vitorpamplona.amethyst.commons.richtext.BechSegment @@ -86,7 +84,6 @@ import com.vitorpamplona.amethyst.commons.richtext.InvoiceSegment import com.vitorpamplona.amethyst.commons.richtext.LinkSegment import com.vitorpamplona.amethyst.commons.richtext.PhoneSegment import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment -import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState import com.vitorpamplona.amethyst.commons.richtext.SchemelessUrlSegment import com.vitorpamplona.amethyst.commons.richtext.Segment @@ -455,34 +452,18 @@ private fun RenderWordWithPreview( fun ImageFromBase64(base64String: String) { val context = LocalContext.current - var base64String2 = base64String.removePrefix("data:image/jpeg;base64,") - RichTextParser.imageExtensions.forEach { - base64String2 = base64String2.removePrefix("data:image/$it;base64,") - } - - val imageBytes = runCatching { Base64.decode(base64String2, Base64.DEFAULT) }.getOrNull() - - if (imageBytes == null) { - BlankNote() - } else { - val request = - ImageRequest.Builder(context) - .data(imageBytes) - .build() - - SubcomposeAsyncImage( - model = request, - contentDescription = null, - contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth(), - ) { - when (painter.state) { - is AsyncImagePainter.State.Success -> { - SubcomposeAsyncImageContent() - } - - else -> BlankNote() + SubcomposeAsyncImage( + model = Base64Requester.imageRequest(context, base64String), + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier.fillMaxWidth(), + ) { + when (painter.state) { + is AsyncImagePainter.State.Success -> { + SubcomposeAsyncImageContent() } + + else -> BlankNote() } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index 1970fee3c..a1de28941 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -48,6 +48,7 @@ import coil.fetch.FetchResult import coil.fetch.Fetcher import coil.request.ImageRequest import coil.request.Options +import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.theme.isLight @@ -182,7 +183,10 @@ class Base64Fetcher( override suspend fun fetch(): FetchResult { checkNotInMainThread() - val base64String = data.toString().removePrefix("data:image/jpeg;base64,") + var base64String = data.toString().removePrefix("data:image/jpeg;base64,") + RichTextParser.imageExtensions.forEach { + base64String = base64String.removePrefix("data:image/$it;base64,") + } val byteArray = Base64.getDecoder().decode(base64String) val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) From e96015bcbcda87bd374cb94ac5cc3c53ff35714b Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 1 Jul 2024 15:07:18 -0300 Subject: [PATCH 4/7] Parse base64 inside the wordIdentifier function --- .../amethyst/commons/richtext/RichTextParser.kt | 16 ++++++++-------- .../commons/richtext/RichTextParserSegments.kt | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt index 44e51df8f..663d1bc6c 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt @@ -127,15 +127,13 @@ class RichTextParser { ): RichTextViewerState { val urlSet = parseValidUrls(content) - val base64Images = parseBase64Images(content) - val imagesForPager = urlSet.mapNotNull { fullUrl -> parseMediaUrl(fullUrl, tags, content, callbackUri) }.associateBy { it.url } val imageList = imagesForPager.values.toList() val emojiMap = Nip30CustomEmoji.createEmojiMap(tags) - val segments = findTextSegments(content, imagesForPager.keys, urlSet, emojiMap, tags, base64Images) + val segments = findTextSegments(content, imagesForPager.keys, urlSet, emojiMap, tags) return RichTextViewerState( urlSet.toImmutableSet(), @@ -143,7 +141,6 @@ class RichTextParser { imageList.toImmutableList(), emojiMap.toImmutableMap(), segments, - base64Images.toImmutableSet(), ) } @@ -153,7 +150,6 @@ class RichTextParser { urls: Set, emojis: Map, tags: ImmutableListOfLists, - base64Images: Set, ): ImmutableList { val lines = content.split('\n') val paragraphSegments = ArrayList(lines.size) @@ -165,7 +161,7 @@ class RichTextParser { val wordList = paragraph.trimEnd().split(' ') val segments = ArrayList(wordList.size) wordList.forEach { word -> - val wordSegment = wordIdentifier(word, images, urls, emojis, tags, base64Images) + val wordSegment = wordIdentifier(word, images, urls, emojis, tags) if (wordSegment !is RegularTextSegment) { isDirty = true } @@ -219,11 +215,15 @@ class RichTextParser { urls: Set, emojis: Map, tags: ImmutableListOfLists, - base64Images: Set, ): Segment { if (word.isEmpty()) return RegularTextSegment(word) - if (base64Images.contains(word)) return Base64Segment(word) + if (word.startsWith("data:image")) { + val base64Images = parseBase64Images(word) + if (base64Images.isNotEmpty()) { + return Base64Segment(word) + } + } if (images.contains(word)) return ImageSegment(word) diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt index e4a38683f..faa1fc462 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt @@ -32,7 +32,6 @@ data class RichTextViewerState( val imageList: ImmutableList, val customEmoji: ImmutableMap, val paragraphs: ImmutableList, - val base64Images: ImmutableSet, ) @Immutable From 9374798f9207c15719e33ee3ea4e1b787ca54deb Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 1 Jul 2024 15:48:22 -0300 Subject: [PATCH 5/7] Revert "Use Base64Requester" This reverts commit 2bd05d80b026d35486cefbce191e99ee2beb162f. --- .../amethyst/ui/components/RichTextViewer.kt | 41 ++++++++++++++----- .../ui/components/RobohashAsyncImage.kt | 6 +-- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 832a68f95..b86990d5c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.components +import android.util.Base64 import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -70,6 +71,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImagePainter import coil.compose.SubcomposeAsyncImage import coil.compose.SubcomposeAsyncImageContent +import coil.request.ImageRequest import com.vitorpamplona.amethyst.commons.compose.produceCachedState import com.vitorpamplona.amethyst.commons.richtext.Base64Segment import com.vitorpamplona.amethyst.commons.richtext.BechSegment @@ -84,6 +86,7 @@ import com.vitorpamplona.amethyst.commons.richtext.InvoiceSegment import com.vitorpamplona.amethyst.commons.richtext.LinkSegment import com.vitorpamplona.amethyst.commons.richtext.PhoneSegment import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment +import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState import com.vitorpamplona.amethyst.commons.richtext.SchemelessUrlSegment import com.vitorpamplona.amethyst.commons.richtext.Segment @@ -452,18 +455,34 @@ private fun RenderWordWithPreview( fun ImageFromBase64(base64String: String) { val context = LocalContext.current - SubcomposeAsyncImage( - model = Base64Requester.imageRequest(context, base64String), - contentDescription = null, - contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth(), - ) { - when (painter.state) { - is AsyncImagePainter.State.Success -> { - SubcomposeAsyncImageContent() - } + var base64String2 = base64String.removePrefix("data:image/jpeg;base64,") + RichTextParser.imageExtensions.forEach { + base64String2 = base64String2.removePrefix("data:image/$it;base64,") + } - else -> BlankNote() + val imageBytes = runCatching { Base64.decode(base64String2, Base64.DEFAULT) }.getOrNull() + + if (imageBytes == null) { + BlankNote() + } else { + val request = + ImageRequest.Builder(context) + .data(imageBytes) + .build() + + SubcomposeAsyncImage( + model = request, + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier.fillMaxWidth(), + ) { + when (painter.state) { + is AsyncImagePainter.State.Success -> { + SubcomposeAsyncImageContent() + } + + else -> BlankNote() + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index a1de28941..1970fee3c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -48,7 +48,6 @@ import coil.fetch.FetchResult import coil.fetch.Fetcher import coil.request.ImageRequest import coil.request.Options -import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.theme.isLight @@ -183,10 +182,7 @@ class Base64Fetcher( override suspend fun fetch(): FetchResult { checkNotInMainThread() - var base64String = data.toString().removePrefix("data:image/jpeg;base64,") - RichTextParser.imageExtensions.forEach { - base64String = base64String.removePrefix("data:image/$it;base64,") - } + val base64String = data.toString().removePrefix("data:image/jpeg;base64,") val byteArray = Base64.getDecoder().decode(base64String) val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) From 2480b437783e2f3f295339a49000b83a58f76a42 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 5 Jul 2024 06:31:58 -0300 Subject: [PATCH 6/7] Decode in the io thread --- .../amethyst/ui/components/RichTextViewer.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index b86990d5c..c7aa2fa0e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.components import android.util.Base64 -import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -32,8 +31,6 @@ import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent import androidx.compose.material3.Icon @@ -115,6 +112,7 @@ import fr.acinq.secp256k1.Hex import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.withContext fun isMarkdown(content: String): Boolean = content.startsWith("> ") || @@ -454,14 +452,18 @@ private fun RenderWordWithPreview( @Composable fun ImageFromBase64(base64String: String) { val context = LocalContext.current - - var base64String2 = base64String.removePrefix("data:image/jpeg;base64,") - RichTextParser.imageExtensions.forEach { - base64String2 = base64String2.removePrefix("data:image/$it;base64,") + var imageBytes by remember { mutableStateOf(null) } + LaunchedEffect(base64String) { + imageBytes = + withContext(Dispatchers.IO) { + var base64String2 = base64String.removePrefix("data:image/jpeg;base64,") + RichTextParser.imageExtensions.forEach { + base64String2 = base64String2.removePrefix("data:image/$it;base64,") + } + runCatching { Base64.decode(base64String2, Base64.DEFAULT) }.getOrNull() + } } - val imageBytes = runCatching { Base64.decode(base64String2, Base64.DEFAULT) }.getOrNull() - if (imageBytes == null) { BlankNote() } else { @@ -480,7 +482,6 @@ fun ImageFromBase64(base64String: String) { is AsyncImagePainter.State.Success -> { SubcomposeAsyncImageContent() } - else -> BlankNote() } } From b730b8c6f646849ce417e7c1cadb7a6e6f0dea85 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 5 Jul 2024 06:56:04 -0300 Subject: [PATCH 7/7] Fix linter --- .../vitorpamplona/amethyst/ui/components/RichTextViewer.kt | 3 ++- .../com/vitorpamplona/amethyst/ui/dal/GeoHashFeedFilter.kt | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index c7aa2fa0e..325628c8c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -468,7 +468,8 @@ fun ImageFromBase64(base64String: String) { BlankNote() } else { val request = - ImageRequest.Builder(context) + ImageRequest + .Builder(context) .data(imageBytes) .build() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/GeoHashFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/GeoHashFeedFilter.kt index f2cd9b72e..2c759ec09 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/GeoHashFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/GeoHashFeedFilter.kt @@ -62,7 +62,9 @@ class GeoHashFeedFilter( it.event is PrivateDmEvent || it.event is PollNoteEvent || it.event is AudioHeaderEvent - ) && it.event?.isTaggedGeoHash(geoTag) == true && account.isAcceptable(it) + ) && + it.event?.isTaggedGeoHash(geoTag) == true && + account.isAcceptable(it) override fun sort(collection: Set): List = collection.sortedWith(DefaultFeedOrder) }