1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-07 15:28:51 +01:00

Initial work for link preview

This commit is contained in:
Tlaster 2020-05-29 18:06:56 +08:00
parent d88e7f331a
commit cc38f01c5e
6 changed files with 172 additions and 1 deletions

View File

@ -367,7 +367,7 @@ private inline val Status.inferredExternalUrl
noticeUriRegex.matchEntire(uri)?.let { result: MatchResult ->
"https://${result.groups[1]?.value}/notice/${result.groups[3]?.value}"
}
}
} ?: entities?.urls?.firstOrNull()?.expandedUrl
private val Status.parcelableLocation: ParcelableLocation?
get() {

View File

@ -0,0 +1,67 @@
package org.mariotaku.twidere.task
import android.content.Context
import androidx.collection.LruCache
import org.attoparser.config.ParseConfiguration
import org.attoparser.dom.DOMMarkupParser
import org.attoparser.dom.Document
import org.mariotaku.ktextension.toString
import org.mariotaku.restfu.http.HttpRequest
import org.mariotaku.twidere.extension.atto.firstElementOrNull
import org.mariotaku.twidere.view.LinkPreviewData
class LinkPreviewTask(
context: Context
) : BaseAbstractTask<String, LinkPreviewData, Any>(context) {
override fun doLongOperation(url: String?): LinkPreviewData? {
if (url == null) {
return null
}
loadingList.add(url)
val response = restHttpClient.newCall(
HttpRequest
.Builder()
.url(url.replace("http:", "https:"))
.method("GET")
.build()
).execute()
//TODO: exception handling
return response.body.stream().toString(charset = Charsets.UTF_8, close = true).let {
val parser = DOMMarkupParser(ParseConfiguration.htmlConfiguration())
parser.parse(it)
}?.let { doc ->
val title = doc.getMeta("og:title")
val desc = doc.getMeta("og:description")
val img = doc.getMeta("og:image")
LinkPreviewData(
title, desc, img
)
}?.also {
cacheData.put(url, it)
loadingList.remove(url)
//TODO: send the result back to bus
}
}
private fun Document.getMeta(name: String): String? {
return firstElementOrNull {
it.elementNameMatches("meta") &&
it.hasAttribute("property") &&
it.getAttributeValue("property") == name
}?.getAttributeValue("content")
}
companion object {
private val loadingList = arrayListOf<String>()
private val cacheData = LruCache<String, LinkPreviewData>(100)
fun isInLoading(url: String): Boolean {
return loadingList.contains(url)
}
fun getCached(url: String): LinkPreviewData? {
return cacheData[url]
}
}
}

View File

@ -0,0 +1,46 @@
package org.mariotaku.twidere.view
import android.annotation.TargetApi
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.layout_link_preview.view.*
import org.mariotaku.twidere.R
data class LinkPreviewData(
val title: String?,
val desc: String?,
val img: String?
)
class LinkPreviewView : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
init {
LayoutInflater.from(context).inflate(R.layout.layout_link_preview, this)
}
fun displayData(value: String, result: LinkPreviewData) {
link_preview_title.isVisible = true
link_preview_link.isVisible = true
link_preview_loader.isVisible = false
link_preview_title.text = result.title
link_preview_link.text = value
}
fun reset() {
link_preview_title.isVisible = false
link_preview_link.isVisible = false
link_preview_loader.isVisible = true
link_preview_title.text = ""
link_preview_link.text = ""
}
}

View File

@ -11,11 +11,13 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.RequestManager
import kotlinx.android.synthetic.main.list_item_status.view.*
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.ktextension.*
import org.mariotaku.microblog.library.mastodon.annotation.StatusVisibility
import org.mariotaku.twidere.Constants.*
@ -36,6 +38,7 @@ import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.task.CreateFavoriteTask
import org.mariotaku.twidere.task.DestroyFavoriteTask
import org.mariotaku.twidere.task.LinkPreviewTask
import org.mariotaku.twidere.task.RetweetStatusTask
import org.mariotaku.twidere.text.TwidereClickableSpan
import org.mariotaku.twidere.util.HtmlEscapeHelper.toPlainText
@ -61,6 +64,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
private val itemContent by lazy { itemView.itemContent }
private val mediaPreview by lazy { itemView.mediaPreview }
private val linkPreview by lazy { itemView.linkPreview }
private val statusContentUpperSpace by lazy { itemView.statusContentUpperSpace }
private val summaryView by lazy { itemView.summary }
private val textView by lazy { itemView.text }
@ -146,6 +150,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
val context = itemView.context
val requestManager = adapter.requestManager
val twitter = adapter.twitterWrapper
val linkify = adapter.twidereLinkify
val formatter = adapter.bidiFormatter
@ -344,6 +349,27 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
mediaPreview.visibility = View.GONE
}
val url = status.extras?.external_url
linkPreview.isVisible = url != null
if (url != null) {
if (!LinkPreviewTask.isInLoading(url)) {
val linkPreviewData = LinkPreviewTask.getCached(url)
if (linkPreviewData != null) {
linkPreview.displayData(url, linkPreviewData)
} else {
LinkPreviewTask(context).let {
it.params = url
TaskStarter.execute(it)
}
linkPreview.reset()
}
} else {
linkPreview.reset()
}
} else {
linkPreview.reset()
}
summaryView.spannable = status.extras?.summary_text
summaryView.hideIfEmpty()

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
xmlns:tools="http://schemas.android.com/tools">
<TextView
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
tools:text="Google"
android:id="@+id/link_preview_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/link_preview_link"
tools:text="https://www.google.com"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<org.mariotaku.chameleon.view.ChameleonProgressBar
tools:visibility="gone"
android:id="@+id/link_preview_loader"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -209,6 +209,13 @@
</org.mariotaku.twidere.view.CardMediaContainer>
<org.mariotaku.twidere.view.LinkPreviewView
android:id="@+id/linkPreview"
android:layout_below="@+id/mediaPreview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<org.mariotaku.twidere.view.ColorLabelRelativeLayout
android:id="@+id/quotedView"
android:layout_width="match_parent"