diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt index cc5b30f6b2..ebd2e3019d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt @@ -63,3 +63,23 @@ object CachedRichTextParser { } } } + +object CachedUrlParser { + private val parsedUrlsCache = LruCache>(10) + + fun cachedParseValidUrls(content: String): List = parsedUrlsCache[content.hashCode()] + + fun parseValidUrls(content: String): List { + if (content.isEmpty()) return emptyList() + + val key = content.hashCode() + val cached = parsedUrlsCache[key] + return if (cached != null) { + cached + } else { + val newUrls = RichTextParser().parseValidUrls(content).toList() + parsedUrlsCache.put(key, newUrls) + newUrls + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlInfoItem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlInfoItem.kt index 720abcdabf..985bdd7541 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlInfoItem.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlInfoItem.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.service.previews import androidx.compose.runtime.Immutable -import okhttp3.MediaType import java.net.URL @Immutable @@ -30,7 +29,7 @@ class UrlInfoItem( val title: String = "", val description: String = "", val image: String = "", - val mimeType: MediaType, + val mimeType: String, ) { val verifiedUrl = kotlin.runCatching { URL(url) }.getOrNull() val imageUrlFullPath = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreview.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreview.kt index 52f4332eb7..b446d947cc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreview.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreview.kt @@ -60,11 +60,11 @@ class UrlPreview { ?: throw IllegalArgumentException("Website returned unknown mimetype: ${it.headers["Content-Type"]}") if (mimeType.type == "text" && mimeType.subtype == "html") { val data = OpenGraphParser().extractUrlInfo(HtmlParser().parseHtml(it.body.source(), mimeType)) - UrlInfoItem(url, data.title, data.description, data.image, mimeType) + UrlInfoItem(url, data.title, data.description, data.image, mimeType.toString()) } else if (mimeType.type == "image") { - UrlInfoItem(url, image = url, mimeType = mimeType) + UrlInfoItem(url, image = url, mimeType = mimeType.toString()) } else if (mimeType.type == "video") { - UrlInfoItem(url, image = url, mimeType = mimeType) + UrlInfoItem(url, image = url, mimeType = mimeType.toString()) } else { throw IllegalArgumentException("Website returned unknown encoding for previews: $mimeType") } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt index 232b31a209..37e032d76c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.actions +import androidx.collection.mutableScatterMapOf import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.Transition @@ -82,10 +83,7 @@ fun Transition.MyCrossfade( content: @Composable (targetState: T) -> Unit, ) { val currentlyVisible = remember { mutableStateListOf().apply { add(currentState) } } - val contentMap = - remember { - mutableMapOf Unit>() - } + val contentMap = remember { mutableScatterMapOf Unit>() } if (currentState == targetState) { // If not animating, just display the current state if (currentlyVisible.size != 1 || currentlyVisible[0] != targetState) { @@ -94,12 +92,11 @@ fun Transition.MyCrossfade( contentMap.clear() } } - if (!contentMap.contains(targetState)) { + + if (targetState !in contentMap) { // Replace target with the same key if any val replacementId = - currentlyVisible.indexOfFirst { - contentKey(it) == contentKey(targetState) - } + currentlyVisible.indexOfFirst { contentKey(it) == contentKey(targetState) } if (replacementId == -1) { currentlyVisible.add(targetState) } else { @@ -108,21 +105,16 @@ fun Transition.MyCrossfade( contentMap.clear() currentlyVisible.fastForEach { stateForContent -> contentMap[stateForContent] = { - val alpha by animateFloat( - transitionSpec = { animationSpec }, - ) { if (it == stateForContent) 1f else 0f } - Box(Modifier.graphicsLayer { this.alpha = alpha }, contentAlignment) { - content(stateForContent) - } + val alpha by + animateFloat(transitionSpec = { animationSpec }) { + if (it == stateForContent) 1f else 0f + } + Box(Modifier.graphicsLayer { this.alpha = alpha }) { content(stateForContent) } } } } Box(modifier, contentAlignment) { - currentlyVisible.fastForEach { - key(contentKey(it)) { - contentMap[it]?.invoke() - } - } + currentlyVisible.fastForEach { key(contentKey(it)) { contentMap[it]?.invoke() } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt index 39b2762b44..f0a83f94f0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt @@ -92,7 +92,7 @@ fun RenderLoaded( callbackUri: String? = null, accountViewModel: AccountViewModel, ) { - if (state.previewInfo.mimeType.type == "image") { + if (state.previewInfo.mimeType.startsWith("image")) { Box(modifier = HalfVertPadding) { ZoomableContentView( content = MediaUrlImage(url, uri = callbackUri), @@ -101,7 +101,7 @@ fun RenderLoaded( accountViewModel = accountViewModel, ) } - } else if (state.previewInfo.mimeType.type == "video") { + } else if (state.previewInfo.mimeType.startsWith("video")) { Box(modifier = HalfVertPadding) { ZoomableContentView( content = MediaUrlVideo(url, uri = callbackUri), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt index 6c51042162..318777ed74 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt @@ -57,7 +57,6 @@ import com.vitorpamplona.quartz.nip92IMeta.imetasByUrl import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -import okhttp3.MediaType.Companion.toMediaType @Composable fun RenderContentAsMarkdown( @@ -375,7 +374,7 @@ fun RenderContentAsMarkdownUserPreview() { image = "https://duckduckgo.com/assets/logo_social-media.png", title = "DuckDuckGo — Privacy, simplified.", description = "The Internet privacy company that empowers you to seamlessly take control of your personal information online, without any tradeoffs.", - mimeType = "text/html".toMediaType(), + mimeType = "text/html", ), ), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/previews/PreviewState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/previews/PreviewState.kt index 53e2ce17d5..8ec4b45f4b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/previews/PreviewState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/previews/PreviewState.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.amethyst.ui.note.creators.previews import androidx.compose.ui.text.input.TextFieldValue -import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.service.CachedUrlParser import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow @@ -36,15 +36,10 @@ class PreviewState { @OptIn(FlowPreview::class) val results = source - .debounce(200) + .debounce(500) + .map { CachedUrlParser.parseValidUrls(it.text) } .distinctUntilChanged() - .map { - if (it.text.isNotEmpty()) { - RichTextParser().parseValidUrls(it.text).toList() - } else { - emptyList() - } - }.flowOn(Dispatchers.Default) + .flowOn(Dispatchers.Default) fun reset() { source.tryEmit(TextFieldValue("")) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/previews/PreviewUrl.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/previews/PreviewUrl.kt index c45d97bd2b..564b34884c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/previews/PreviewUrl.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/previews/PreviewUrl.kt @@ -53,6 +53,7 @@ import com.vitorpamplona.amethyst.ui.components.WaitAndDisplay import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import kotlin.text.startsWith @Composable fun PreviewUrl( @@ -201,17 +202,17 @@ private fun MyLoadUrlPreviewDirect( ) { state -> when (state) { is UrlPreviewState.Loaded -> { - if (state.previewInfo.mimeType.type == "image") { + if (state.previewInfo.mimeType.startsWith("image")) { AsyncImage( model = state.previewInfo.url, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.fillMaxHeight().aspectRatio(1f), ) - } else if (state.previewInfo.mimeType.type == "video") { + } else if (state.previewInfo.mimeType.startsWith("video")) { VideoView( state.previewInfo.url, - mimeType = state.previewInfo.mimeType.toString(), + mimeType = state.previewInfo.mimeType, roundedCorner = false, gallery = false, contentScale = ContentScale.Crop, @@ -268,17 +269,17 @@ private fun MyLoadUrlPreviewDirectFillWidth( ) { state -> when (state) { is UrlPreviewState.Loaded -> { - if (state.previewInfo.mimeType.type == "image") { + if (state.previewInfo.mimeType.startsWith("image")) { AsyncImage( model = state.previewInfo.url, contentDescription = null, contentScale = ContentScale.FillWidth, modifier = Modifier.fillMaxWidth(), ) - } else if (state.previewInfo.mimeType.type == "video") { + } else if (state.previewInfo.mimeType.startsWith("video")) { VideoView( state.previewInfo.url, - mimeType = state.previewInfo.mimeType.toString(), + mimeType = state.previewInfo.mimeType, roundedCorner = false, gallery = false, contentScale = ContentScale.FillWidth,