diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/SharedPreferenceConstants.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/SharedPreferenceConstants.java index 022bedf1d..5cc0c2d2e 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/SharedPreferenceConstants.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/SharedPreferenceConstants.java @@ -291,6 +291,8 @@ public interface SharedPreferenceConstants { String KEY_AUTO_HIDE_TABS = "auto_hide_tabs"; @ExportablePreference(BOOLEAN) String KEY_HIDE_CARD_NUMBERS = "hide_card_numbers"; + @ExportablePreference(BOOLEAN) + String KEY_SHOW_LINK_PREVIEW = "show_link_preview"; // Internal preferences diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java index 0239de25c..eb7598626 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java @@ -20,11 +20,11 @@ package org.mariotaku.twidere.model; import android.os.Parcel; import android.os.Parcelable; -import androidx.annotation.IntDef; +import android.text.TextUtils; + import androidx.annotation.LongDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.text.TextUtils; import com.bluelinelabs.logansquare.annotation.JsonField; import com.bluelinelabs.logansquare.annotation.JsonObject; @@ -527,6 +527,9 @@ public class ParcelableStatus implements Parcelable, Comparable = HashSet() @@ -267,6 +268,10 @@ abstract class ParcelableStatusesAdapter( return showCardNumbers || showingActionCardId == getItemId(position) } + override fun isLinkPreviewShown(position: Int): Boolean { + return showLinkPreview + } + override fun isCardActionsShown(position: Int): Boolean { if (position == RecyclerView.NO_POSITION) return showCardActions return showCardActions || showingActionCardId == getItemId(position) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/StatusDetailsAdapter.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/StatusDetailsAdapter.kt index 693423eac..00090202c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/StatusDetailsAdapter.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/StatusDetailsAdapter.kt @@ -19,7 +19,6 @@ package org.mariotaku.twidere.adapter -import androidx.recyclerview.widget.RecyclerView import android.text.TextUtils import android.text.method.LinkMovementMethod import android.util.SparseBooleanArray @@ -28,6 +27,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Space import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView import org.mariotaku.kpreferences.get import org.mariotaku.ktextension.contains import org.mariotaku.microblog.library.twitter.model.TranslationResult @@ -72,6 +72,7 @@ class StatusDetailsAdapter( private val cardBackgroundColor: Int private val showCardActions = !preferences[hideCardActionsKey] private val showCardNumbers = !preferences[hideCardNumbersKey] + private val showLinkPreview = preferences[showLinkPreviewKey] private var recyclerView: RecyclerView? = null private var detailMediaExpanded: Boolean = false @@ -179,6 +180,10 @@ class StatusDetailsAdapter( return showCardNumbers || showingActionCardPosition == position } + override fun isLinkPreviewShown(position: Int): Boolean { + return showLinkPreview + } + override fun isCardActionsShown(position: Int): Boolean { if (position == RecyclerView.NO_POSITION) return showCardActions return showCardActions || showingActionCardPosition == position diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/iface/IStatusesAdapter.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/iface/IStatusesAdapter.kt index e3c692556..053e622c2 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/iface/IStatusesAdapter.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/iface/IStatusesAdapter.kt @@ -34,6 +34,8 @@ interface IStatusesAdapter : IContentAdapter, IGapSupportedAdapter { val statusClickListener: IStatusViewHolder.StatusClickListener? fun isCardNumbersShown(position: Int): Boolean + + fun isLinkPreviewShown(position: Int): Boolean fun isCardActionsShown(position: Int): Boolean diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt index 0432b03a2..f1e2e5539 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt @@ -90,6 +90,7 @@ val tabPositionKey = KStringKey(KEY_TAB_POSITION, SharedPreferenceConstants.DEFA val yandexKeyKey = KStringKey(SharedPreferenceConstants.KEY_YANDEX_KEY, TwidereConstants.YANDEX_KEY) val autoHideTabs = KBooleanKey(SharedPreferenceConstants.KEY_AUTO_HIDE_TABS, true) val hideCardNumbersKey = KBooleanKey(KEY_HIDE_CARD_NUMBERS, false) +val showLinkPreviewKey = KBooleanKey(KEY_SHOW_LINK_PREVIEW, false) object cacheSizeLimitKey : KSimpleKey(KEY_CACHE_SIZE_LIMIT, 300) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt index 61dad0302..42e8f87f4 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt @@ -46,6 +46,7 @@ import org.mariotaku.twidere.util.HtmlSpanBuilder import org.mariotaku.twidere.util.InternalTwitterContentUtils import org.mariotaku.twidere.util.InternalTwitterContentUtils.getMediaUrl import org.mariotaku.twidere.util.InternalTwitterContentUtils.getStartEndForEntity +import kotlin.math.max fun Status.toParcelable(details: AccountDetails, profileImageSize: String = "normal", updateFilterInfoAction: (Status, ParcelableStatus) -> Unit = ::updateFilterInfoDefault): ParcelableStatus { @@ -71,6 +72,13 @@ fun Status.applyTo(accountKey: UserKey, accountType: String, profileImageSize: S result.timestamp = createdAt?.time ?: 0 extras.external_url = inferredExternalUrl + extras.entities_url = entities?.urls?.map { it.expandedUrl }?.let { + if (isQuoteStatus) { + it.take(max(0, it.count() - 1)) + } else { + it + } + }?.toTypedArray() extras.support_entities = entities != null extras.statusnet_conversation_id = statusnetConversationId extras.conversation_id = conversationId @@ -367,7 +375,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() { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt index c05c0a967..eaf20fd26 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt @@ -25,6 +25,7 @@ import android.content.ContentValues import android.content.Context import android.content.Intent import android.graphics.Rect +import android.net.Uri import android.os.Bundle import androidx.annotation.CallSuper import androidx.fragment.app.Fragment @@ -465,6 +466,12 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment - val title = doc.getMeta("og:title") + val title = doc.getMeta("og:title") ?: doc.title() val desc = doc.getMeta("og:description") val img = doc.getMeta("og:image") LinkPreviewData( @@ -40,16 +39,15 @@ class LinkPreviewTask( }?.also { cacheData.put(url, it) loadingList.remove(url) - //TODO: send the result back to bus } } + override fun afterExecute(callback: Any?, result: LinkPreviewData?) { + bus.post(StatusListChangedEvent()) + } + private fun Document.getMeta(name: String): String? { - return firstElementOrNull { - it.elementNameMatches("meta") && - it.hasAttribute("property") && - it.getAttributeValue("property") == name - }?.getAttributeValue("content") + return this.head().getElementsByAttributeValue("property", name).firstOrNull { it.tagName() == "meta" }?.attributes()?.get("content") } companion object { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/LinkPreviewView.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/LinkPreviewView.kt index f813a32ef..de4bfde9d 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/LinkPreviewView.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/LinkPreviewView.kt @@ -1,42 +1,49 @@ package org.mariotaku.twidere.view -import android.annotation.TargetApi import android.content.Context -import android.os.Build +import android.net.Uri import android.util.AttributeSet import android.view.LayoutInflater -import android.widget.FrameLayout import androidx.core.view.isVisible +import com.bumptech.glide.RequestManager +import com.google.android.material.card.MaterialCardView 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? + val desc: String? = null, + val img: String? = null, + val imgRes: Int? = null ) -class LinkPreviewView : FrameLayout { +class LinkPreviewView : MaterialCardView { 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) + LayoutInflater.from(context).inflate(R.layout.layout_link_preview, this, true) } - fun displayData(value: String, result: LinkPreviewData) { + fun displayData(value: String, result: LinkPreviewData, requestManager: RequestManager) { link_preview_title.isVisible = true link_preview_link.isVisible = true + link_preview_img.isVisible = result.img != null link_preview_loader.isVisible = false link_preview_title.text = result.title - link_preview_link.text = value + link_preview_link.text = Uri.parse(value).host + if (result.img != null) { + requestManager.load(result.img).into(link_preview_img) + } else if (result.imgRes != null) { + requestManager.load(result.imgRes).into(link_preview_img) + } } fun reset() { + link_preview_img.isVisible = false link_preview_title.isVisible = false link_preview_link.isVisible = false link_preview_loader.isVisible = true diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/StatusViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/StatusViewHolder.kt index 1d4edcf3b..56424d6a1 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/StatusViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/StatusViewHolder.kt @@ -47,6 +47,7 @@ import org.mariotaku.twidere.util.ThemeUtils import org.mariotaku.twidere.util.UnitConvertUtils import org.mariotaku.twidere.util.Utils import org.mariotaku.twidere.util.Utils.getUserTypeIconRes +import org.mariotaku.twidere.view.LinkPreviewData import org.mariotaku.twidere.view.ShapedImageView import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder import java.lang.ref.WeakReference @@ -143,6 +144,8 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View) quotedMediaPreview.visibility = View.GONE quotedMediaLabel.visibility = View.GONE mediaPreview.displayMedia(R.drawable.featured_graphics) + linkPreview.isVisible = isLinkPreviewShown + linkPreview.displayData(TWIDERE_PREVIEW_LINK_URI, LinkPreviewData(title = TWIDERE_PREVIEW_NAME, imgRes = R.drawable.featured_graphics), adapter.requestManager) } override fun display(status: ParcelableStatus, displayInReplyTo: Boolean, @@ -349,13 +352,13 @@ 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) { + val url = status.extras?.entities_url?.firstOrNull() + linkPreview.isVisible = url != null && isLinkPreviewShown + if (url != null && linkPreview.isVisible) { if (!LinkPreviewTask.isInLoading(url)) { val linkPreviewData = LinkPreviewTask.getCached(url) if (linkPreviewData != null) { - linkPreview.displayData(url, linkPreviewData) + linkPreview.displayData(url, linkPreviewData, requestManager) } else { LinkPreviewTask(context).let { it.params = url @@ -510,6 +513,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View) itemContent.setOnClickListener(eventListener) itemContent.setOnLongClickListener(eventListener) + linkPreview.setOnClickListener(eventListener) itemMenu.setOnClickListener(eventListener) profileImageView.setOnClickListener(eventListener) replyButton.setOnClickListener(eventListener) @@ -601,6 +605,8 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View) listener.onLiked() } } + private val isLinkPreviewShown: Boolean + get() = adapter.isLinkPreviewShown(layoutPosition) private val isCardNumbersShown: Boolean get() = adapter.isCardNumbersShown(layoutPosition) @@ -724,6 +730,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View) listener.onStatusClick(holder, position) } } + holder.linkPreview -> { + listener.onLinkClick(holder, position) + } } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/iface/IStatusViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/iface/IStatusViewHolder.kt index 4e9b30b75..98362eac9 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/iface/IStatusViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/iface/IStatusViewHolder.kt @@ -65,6 +65,8 @@ interface IStatusViewHolder : CardMediaContainer.OnMediaClickListener { fun onUserProfileClick(holder: IStatusViewHolder, position: Int) {} fun onFilterClick(holder: TimelineFilterHeaderViewHolder) {} + + fun onLinkClick(holder: IStatusViewHolder, position: Int) {} } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/DetailStatusViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/DetailStatusViewHolder.kt index 896551bce..db5845b96 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/DetailStatusViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/DetailStatusViewHolder.kt @@ -22,6 +22,7 @@ package org.mariotaku.twidere.view.holder.status import android.content.Context import android.content.SharedPreferences import android.graphics.Rect +import android.net.Uri import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.Spanned @@ -36,10 +37,12 @@ import androidx.annotation.UiThread import androidx.appcompat.widget.ActionMenuView import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat +import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.adapter_item_status_count_label.view.* import kotlinx.android.synthetic.main.header_status.view.* +import org.mariotaku.abstask.library.TaskStarter import org.mariotaku.kpreferences.get import org.mariotaku.ktextension.applyFontFamily import org.mariotaku.ktextension.hideIfEmpty @@ -54,6 +57,7 @@ import org.mariotaku.twidere.annotation.ProfileImageSize import org.mariotaku.twidere.constant.displaySensitiveContentsKey import org.mariotaku.twidere.constant.hideCardNumbersKey import org.mariotaku.twidere.constant.newDocumentApiKey +import org.mariotaku.twidere.constant.showLinkPreviewKey import org.mariotaku.twidere.extension.loadProfileImage import org.mariotaku.twidere.extension.model.* import org.mariotaku.twidere.fragment.AbsStatusesFragment @@ -63,6 +67,7 @@ import org.mariotaku.twidere.menu.RetweetItemProvider import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.util.ParcelableLocationUtils import org.mariotaku.twidere.model.util.ParcelableMediaUtils +import org.mariotaku.twidere.task.LinkPreviewTask import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.twitter.card.TwitterCardViewFactory import org.mariotaku.twidere.view.ProfileImageView @@ -366,6 +371,28 @@ class DetailStatusViewHolder( textView.movementMethod = LinkMovementMethod.getInstance() itemView.quotedText.movementMethod = null + + + val url = status.extras?.entities_url?.firstOrNull() + itemView.linkPreview.isVisible = url != null && fragment.preferences[showLinkPreviewKey] + if (url != null && itemView.linkPreview.isVisible) { + if (!LinkPreviewTask.isInLoading(url)) { + val linkPreviewData = LinkPreviewTask.getCached(url) + if (linkPreviewData != null) { + itemView.linkPreview.displayData(url, linkPreviewData, adapter.requestManager) + } else { + LinkPreviewTask(context).let { + it.params = url + TaskStarter.execute(it) + } + itemView.linkPreview.reset() + } + } else { + itemView.linkPreview.reset() + } + } else { + itemView.linkPreview.reset() + } } override fun onClick(v: View) { @@ -373,6 +400,10 @@ class DetailStatusViewHolder( val fragment = adapter.fragment val preferences = fragment.preferences when (v) { + itemView.linkPreview -> { + val url = status.extras?.entities_url?.firstOrNull() + OnLinkClickHandler.openLink(fragment.requireContext(), preferences, Uri.parse(url)) + } itemView.mediaPreviewLoad -> { if (adapter.sensitiveContentEnabled || !status.is_possibly_sensitive) { adapter.isDetailMediaExpanded = true @@ -472,6 +503,7 @@ class DetailStatusViewHolder( ThemeUtils.wrapMenuIcon(itemView.menuBar, excludeGroups = *intArrayOf(Constants.MENU_GROUP_STATUS_SHARE)) itemView.mediaPreviewLoad.setOnClickListener(this) itemView.profileContainer.setOnClickListener(this) + itemView.linkPreview.setOnClickListener(this) retweetedByView.setOnClickListener(this) locationView.setOnClickListener(this) itemView.quotedView.setOnClickListener(this) diff --git a/twidere/src/main/res/layout/header_status.xml b/twidere/src/main/res/layout/header_status.xml index 02893de45..ad45e9290 100644 --- a/twidere/src/main/res/layout/header_status.xml +++ b/twidere/src/main/res/layout/header_status.xml @@ -290,11 +290,18 @@ + + - - - - - \ No newline at end of file + android:layout_height="wrap_content"> + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/list_item_status.xml b/twidere/src/main/res/layout/list_item_status.xml index 05dae4b48..b2959556d 100644 --- a/twidere/src/main/res/layout/list_item_status.xml +++ b/twidere/src/main/res/layout/list_item_status.xml @@ -212,6 +212,7 @@ @@ -220,7 +221,7 @@ android:id="@+id/quotedView" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@+id/mediaPreview" + android:layout_below="@+id/linkPreview" android:layout_marginTop="@dimen/element_spacing_small" android:background="?selectableItemBackground" android:clickable="true" @@ -314,6 +315,7 @@ + diff --git a/twidere/src/main/res/values/strings.xml b/twidere/src/main/res/values/strings.xml index 602e7a6a2..f540d5201 100644 --- a/twidere/src/main/res/values/strings.xml +++ b/twidere/src/main/res/values/strings.xml @@ -446,6 +446,7 @@ Hide card actions Hide card numbers + Show link preview Hide quotes Hide replies Hide retweets diff --git a/twidere/src/main/res/xml/network_security_config.xml b/twidere/src/main/res/xml/network_security_config.xml index 21083def2..6684a7724 100644 --- a/twidere/src/main/res/xml/network_security_config.xml +++ b/twidere/src/main/res/xml/network_security_config.xml @@ -1,6 +1,5 @@ - - fanfou.com - + + diff --git a/twidere/src/main/res/xml/preferences_cards.xml b/twidere/src/main/res/xml/preferences_cards.xml index 65ff7da47..f1392ee57 100644 --- a/twidere/src/main/res/xml/preferences_cards.xml +++ b/twidere/src/main/res/xml/preferences_cards.xml @@ -111,8 +111,18 @@ + + + +