mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 16:33:27 +00:00
Caches the processing of preview urls for the new post screen
Fixes a bug on saving MediaType on Android's stack
This commit is contained in:
@@ -63,3 +63,23 @@ object CachedRichTextParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object CachedUrlParser {
|
||||
private val parsedUrlsCache = LruCache<Int, List<String>>(10)
|
||||
|
||||
fun cachedParseValidUrls(content: String): List<String> = parsedUrlsCache[content.hashCode()]
|
||||
|
||||
fun parseValidUrls(content: String): List<String> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
+11
-19
@@ -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 <T> Transition<T>.MyCrossfade(
|
||||
content: @Composable (targetState: T) -> Unit,
|
||||
) {
|
||||
val currentlyVisible = remember { mutableStateListOf<T>().apply { add(currentState) } }
|
||||
val contentMap =
|
||||
remember {
|
||||
mutableMapOf<T, @Composable () -> Unit>()
|
||||
}
|
||||
val contentMap = remember { mutableScatterMapOf<T, @Composable () -> Unit>() }
|
||||
if (currentState == targetState) {
|
||||
// If not animating, just display the current state
|
||||
if (currentlyVisible.size != 1 || currentlyVisible[0] != targetState) {
|
||||
@@ -94,12 +92,11 @@ fun <T> Transition<T>.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 <T> Transition<T>.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() } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
+1
-2
@@ -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",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
+4
-9
@@ -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(""))
|
||||
|
||||
+7
-6
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user