Detecting images by pinging the server instead of relying on the url.
This commit is contained in:
@@ -34,7 +34,7 @@ object UrlCachedPreviewer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val state = if (urlInfo.allFetchComplete() && urlInfo.url == url) {
|
val state = if (urlInfo.fetchComplete() && urlInfo.url == url) {
|
||||||
UrlPreviewState.Loaded(urlInfo)
|
UrlPreviewState.Loaded(urlInfo)
|
||||||
} else {
|
} else {
|
||||||
UrlPreviewState.Empty
|
UrlPreviewState.Empty
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
package com.vitorpamplona.amethyst.service.previews
|
package com.vitorpamplona.amethyst.service.previews
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
|
class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
|
||||||
private val imageExtensionArray = arrayOf(".gif", ".png", ".jpg", ".jpeg", ".bmp", ".webp")
|
|
||||||
|
|
||||||
suspend fun fetchUrlPreview(timeOut: Int = 30000) = withContext(Dispatchers.IO) {
|
suspend fun fetchUrlPreview(timeOut: Int = 30000) = withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
fetch(timeOut)
|
fetch(timeOut)
|
||||||
@@ -17,26 +13,7 @@ class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetch(timeOut: Int = 30000) {
|
private suspend fun fetch(timeOut: Int = 30000) {
|
||||||
lateinit var urlInfoItem: UrlInfoItem
|
callback?.onComplete(getDocument(url, timeOut))
|
||||||
if (checkIsImageUrl()) {
|
|
||||||
urlInfoItem = UrlInfoItem(url = url, image = url)
|
|
||||||
} else {
|
|
||||||
val document = getDocument(url, timeOut)
|
|
||||||
urlInfoItem = parseHtml(url, document)
|
|
||||||
}
|
|
||||||
callback?.onComplete(urlInfoItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkIsImageUrl(): Boolean {
|
|
||||||
val uri = Uri.parse(url)
|
|
||||||
var isImage = false
|
|
||||||
for (imageExtension in imageExtensionArray) {
|
|
||||||
if (uri.path != null && uri.path!!.toLowerCase(Locale.getDefault()).endsWith(imageExtension)) {
|
|
||||||
isImage = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isImage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cleanUp() {
|
fun cleanUp() {
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package com.vitorpamplona.amethyst.service.previews
|
package com.vitorpamplona.amethyst.service.previews
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import okhttp3.MediaType
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class UrlInfoItem(
|
class UrlInfoItem(
|
||||||
val url: String = "",
|
val url: String = "",
|
||||||
val title: String = "",
|
val title: String = "",
|
||||||
val description: String = "",
|
val description: String = "",
|
||||||
val image: String = ""
|
val image: String = "",
|
||||||
|
val mimeType: MediaType
|
||||||
) {
|
) {
|
||||||
val verifiedUrl = kotlin.runCatching { URL(url) }.getOrNull()
|
val verifiedUrl = kotlin.runCatching { URL(url) }.getOrNull()
|
||||||
val imageUrlFullPath =
|
val imageUrlFullPath =
|
||||||
@@ -18,6 +20,10 @@ data class UrlInfoItem(
|
|||||||
image
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fetchComplete(): Boolean {
|
||||||
|
return url.isNotEmpty() && image.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
fun allFetchComplete(): Boolean {
|
fun allFetchComplete(): Boolean {
|
||||||
return title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()
|
return title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.vitorpamplona.amethyst.service.previews
|
|||||||
import com.vitorpamplona.amethyst.service.HttpClient
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.MediaType
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
@@ -46,21 +48,31 @@ private val META_X_IMAGE = arrayOf(
|
|||||||
|
|
||||||
private const val CONTENT = "content"
|
private const val CONTENT = "content"
|
||||||
|
|
||||||
suspend fun getDocument(url: String, timeOut: Int = 30000): Document =
|
suspend fun getDocument(url: String, timeOut: Int = 30000): UrlInfoItem =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val request: Request = Request.Builder().url(url).get().build()
|
val request: Request = Request.Builder().url(url).get().build()
|
||||||
val html = HttpClient.getHttpClient().newCall(request).execute().use {
|
HttpClient.getHttpClient().newCall(request).execute().use {
|
||||||
if (it.isSuccessful) {
|
if (it.isSuccessful) {
|
||||||
it.body.string()
|
val mimeType = it.headers.get("Content-Type")?.toMediaType()
|
||||||
|
?: throw IllegalArgumentException("Website returned unknown mimetype: ${it.headers.get("Content-Type")}")
|
||||||
|
|
||||||
|
if (mimeType.type == "text" && mimeType.subtype == "html") {
|
||||||
|
val document = Jsoup.parse(it.body.string())
|
||||||
|
parseHtml(url, document, mimeType)
|
||||||
|
} else if (mimeType.type == "image") {
|
||||||
|
UrlInfoItem(url, image = url, mimeType = mimeType)
|
||||||
|
} else if (mimeType.type == "video") {
|
||||||
|
UrlInfoItem(url, image = url, mimeType = mimeType)
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Website returned unknown encoding for previews: $mimeType")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException("Website returned: " + it.code)
|
throw IllegalArgumentException("Website returned: " + it.code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Jsoup.parse(html)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun parseHtml(url: String, document: Document): UrlInfoItem =
|
suspend fun parseHtml(url: String, document: Document, type: MediaType): UrlInfoItem =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val metaTags = document.getElementsByTag(ELEMENT_TAG_META)
|
val metaTags = document.getElementsByTag(ELEMENT_TAG_META)
|
||||||
|
|
||||||
@@ -106,8 +118,8 @@ suspend fun parseHtml(url: String, document: Document): UrlInfoItem =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()) {
|
if (title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()) {
|
||||||
return@withContext UrlInfoItem(url, title, description, image)
|
return@withContext UrlInfoItem(url, title, description, image, type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@withContext UrlInfoItem(url, title, description, image)
|
return@withContext UrlInfoItem(url, title, description, image, type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.components
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -10,6 +11,8 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoadUrlPreview(url: String, urlText: String, accountViewModel: AccountViewModel) {
|
fun LoadUrlPreview(url: String, urlText: String, accountViewModel: AccountViewModel) {
|
||||||
@@ -42,7 +45,17 @@ fun LoadUrlPreview(url: String, urlText: String, accountViewModel: AccountViewMo
|
|||||||
) { state ->
|
) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is UrlPreviewState.Loaded -> {
|
is UrlPreviewState.Loaded -> {
|
||||||
UrlPreviewCard(url, state.previewInfo)
|
if (state.previewInfo.mimeType.type == "image") {
|
||||||
|
Box(modifier = HalfVertPadding) {
|
||||||
|
ZoomableContentView(ZoomableUrlImage(url), persistentListOf(), roundedCorner = true, accountViewModel)
|
||||||
|
}
|
||||||
|
} else if (state.previewInfo.mimeType.type == "video") {
|
||||||
|
Box(modifier = HalfVertPadding) {
|
||||||
|
ZoomableContentView(ZoomableUrlVideo(url), persistentListOf(), roundedCorner = true, accountViewModel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UrlPreviewCard(url, state.previewInfo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user