android: Link preview with image URLs and more options for finding image on a site (#1207)

This commit is contained in:
Stanislav Dmitrenko
2022-10-13 21:11:59 +03:00
committed by GitHub
parent 799d9443bd
commit 4a259e44b1
2 changed files with 59 additions and 12 deletions
@@ -294,6 +294,9 @@ fun ComposeView(
if (lp != null && pendingLinkUrl.value == url) {
composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(lp))
pendingLinkUrl.value = null
} else if (pendingLinkUrl.value == url) {
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
pendingLinkUrl.value = null
}
}
}
@@ -26,29 +26,44 @@ import chat.simplex.app.views.chat.item.SentColorLight
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jsoup.Jsoup
import java.net.URL
private const val OG_SELECT_QUERY = "meta[property^=og:]"
private const val ICON_SELECT_QUERY = "link[rel^=icon],link[rel^=apple-touch-icon],link[rel^=shortcut icon]"
private val IMAGE_SUFFIXES = listOf(".jpg", ".png", ".ico", ".webp", ".gif")
suspend fun getLinkPreview(url: String): LinkPreview? {
return withContext(Dispatchers.IO) {
try {
val response = Jsoup.connect(url)
.ignoreContentType(true)
.timeout(10000)
.followRedirects(true)
.execute()
val doc = response.parse()
val ogTags = doc.select(OG_SELECT_QUERY)
val imageUri = ogTags.firstOrNull { it.attr("property") == "og:image" }?.attr("content")
val title: String?
val u = kotlin.runCatching { URL(url) }.getOrNull() ?: return@withContext null
var imageUri = when {
IMAGE_SUFFIXES.any { u.path.lowercase().endsWith(it) } -> {
title = u.path.substringAfterLast("/")
url
}
else -> {
val response = Jsoup.connect(url)
.ignoreContentType(true)
.timeout(10000)
.followRedirects(true)
.execute()
val doc = response.parse()
val ogTags = doc.select(OG_SELECT_QUERY)
title = ogTags.firstOrNull { it.attr("property") == "og:title" }?.attr("content") ?: doc.title()
ogTags.firstOrNull { it.attr("property") == "og:image" }?.attr("content")
?: doc.select(ICON_SELECT_QUERY).firstOrNull { it.attr("rel").contains("icon") }?.attr("href")
}
}
if (imageUri != null) {
imageUri = normalizeImageUri(u, imageUri)
try {
val stream = java.net.URL(imageUri).openStream()
val stream = URL(imageUri).openStream()
val image = resizeImageToStrSize(BitmapFactory.decodeStream(stream), maxDataSize = 14000)
// TODO add once supported in iOS
// val description = ogTags.firstOrNull {
// it.attr("property") == "og:description"
// }?.attr("content") ?: ""
val title = ogTags.firstOrNull { it.attr("property") == "og:title" }?.attr("content")
if (title != null) {
return@withContext LinkPreview(url, title, description = "", image)
}
@@ -63,8 +78,6 @@ suspend fun getLinkPreview(url: String): LinkPreview? {
}
}
@Composable
fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit) {
Row(
@@ -127,6 +140,37 @@ fun ChatItemLinkView(linkPreview: LinkPreview) {
}
}
private fun normalizeImageUri(u: URL, imageUri: String) = when {
!imageUri.lowercase().startsWith("http") -> {
"${u.protocol}://${u.host}" +
if (imageUri.startsWith("/"))
imageUri
else
// When an icon is used as an image with relative href path like: site=site.com, <link rel="icon" href="icon.png">
if (u.path.endsWith("/")) u.path + imageUri
else u.path.substringBeforeLast("/") + "/$imageUri"
}
else -> imageUri
}
/*fun normalizeImageUriTest() {
val expect = mapOf<Pair<URL, String>, String>(
URL("https://example.com") to "icon.png" to "https://example.com/icon.png",
URL("https://example.com/") to "icon.png" to "https://example.com/icon.png",
URL("https://example.com/") to "/icon.png" to "https://example.com/icon.png",
URL("https://example.com") to "assets/images/favicon.png" to "https://example.com/assets/images/favicon.png",
URL("https://example.com/") to "assets/images/favicon.png" to "https://example.com/assets/images/favicon.png",
URL("https://example.com/dir") to "/favicon.png" to "https://example.com/favicon.png",
URL("https://example.com/dir/") to "favicon.png" to "https://example.com/dir/favicon.png",
URL("https://example.com/dir/") to "/favicon.png" to "https://example.com/favicon.png",
URL("https://example.com/dir/page") to "favicon.png" to "https://example.com/dir/favicon.png",
URL("https://example.com/abcde.gif") to "https://example.com/abcde.gif" to "https://example.com/abcde.gif",
URL("https://example.com/abcde.gif?a=b") to "https://example.com/abcde.gif?a=b" to "https://example.com/abcde.gif?a=b",
)
expect.forEach {
Log.d(TAG, "Image URI ${normalizeImageUri(it.key.first, it.key.second)} == ${normalizeImageUri(it.key.first, it.key.second) == it.value}")
}
}*/
@Preview(showBackground = true)
@Preview(