complete link preview

This commit is contained in:
Tlaster 2020-06-02 17:15:34 +08:00
parent cc38f01c5e
commit d345ad3d25
22 changed files with 199 additions and 71 deletions

View File

@ -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

View File

@ -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<ParcelableStatus
@JsonField(name = "external_url")
public String external_url;
@JsonField(name = "entities_url")
public String[] entities_url;
@JsonField(name = "quoted_external_url")
public String quoted_external_url;

View File

@ -230,6 +230,7 @@ dependencies {
implementation "com.google.dagger:dagger:${libVersions['Dagger']}"
kapt "com.google.dagger:dagger-compiler:${libVersions['Dagger']}"
implementation 'org.attoparser:attoparser:2.0.4.RELEASE'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.9.1'
implementation 'net.ypresto.androidtranscoder:android-transcoder:0.2.0'
implementation "com.google.android.exoplayer:exoplayer-core:${libVersions['Exoplayer']}"

View File

@ -91,6 +91,7 @@ public interface Constants extends TwidereConstants {
String TWIDERE_PREVIEW_NAME = "Twidere Project";
String TWIDERE_PREVIEW_SCREEN_NAME = "TwidereProject";
String TWIDERE_PREVIEW_TEXT_HTML = "Twidere is an open source twitter client for Android, see <a href='https://github.com/mariotaku/twidere'>github.com/mariotak&#8230;</a>";
String TWIDERE_PREVIEW_LINK_URI = "https://github.com/TwidereProject/Twidere-Android";
String TWIDERE_PREVIEW_TEXT_UNESCAPED = "Twidere is an open source twitter client for Android, see github.com/mariotak&#8230;";
String TWIDERE_PREVIEW_SOURCE = "Twidere for Android";

View File

@ -65,6 +65,8 @@ class DummyItemAdapter(
var showCardNumbers: Boolean = false
var showLinkPreview: Boolean = false
private var showingActionCardPosition = RecyclerView.NO_POSITION
private val showingFullTextStates = SparseBooleanArray()
@ -110,6 +112,10 @@ class DummyItemAdapter(
return showCardActions || showingActionCardPosition == position
}
override fun isLinkPreviewShown(position: Int): Boolean {
return showLinkPreview
}
override fun showCardActions(position: Int) {
if (showingActionCardPosition != RecyclerView.NO_POSITION && adapter != null) {
adapter.notifyItemChanged(showingActionCardPosition)
@ -189,6 +195,7 @@ class DummyItemAdapter(
sensitiveContentEnabled = preferences[displaySensitiveContentsKey]
showCardActions = !preferences[hideCardActionsKey]
showCardNumbers = !preferences[hideCardNumbersKey]
showLinkPreview = preferences[showLinkPreviewKey]
linkHighlightingStyle = preferences[linkHighlightOptionKey]
lightFont = preferences[lightFontKey]
useStarsForLikes = preferences[iWantMyStarsBackKey]

View File

@ -21,11 +21,11 @@ package org.mariotaku.twidere.adapter
import android.content.Context
import android.database.CursorIndexOutOfBoundsException
import androidx.legacy.widget.Space
import androidx.recyclerview.widget.RecyclerView
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.legacy.widget.Space
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.*
@ -80,6 +80,7 @@ abstract class ParcelableStatusesAdapter(
final override val sensitiveContentEnabled: Boolean = preferences.getBoolean(KEY_DISPLAY_SENSITIVE_CONTENTS, false)
private val showCardActions: Boolean = !preferences[hideCardActionsKey]
private val showCardNumbers: Boolean = !preferences[hideCardNumbersKey]
private val showLinkPreview: Boolean = preferences[showLinkPreviewKey]
private val gapLoadingIds: MutableSet<ObjectId> = 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)

View File

@ -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

View File

@ -34,6 +34,8 @@ interface IStatusesAdapter<in Data> : IContentAdapter, IGapSupportedAdapter {
val statusClickListener: IStatusViewHolder.StatusClickListener?
fun isCardNumbersShown(position: Int): Boolean
fun isLinkPreviewShown(position: Int): Boolean
fun isCardActionsShown(position: Int): Boolean

View File

@ -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<Int>(KEY_CACHE_SIZE_LIMIT, 300) {

View File

@ -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() {

View File

@ -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<Parcelab
startActivity(intent)
}
override fun onLinkClick(holder: IStatusViewHolder, position: Int) {
val status = adapter.getStatus(position)
val url = status.extras?.entities_url?.firstOrNull()
OnLinkClickHandler.openLink(requireContext(), preferences, Uri.parse(url))
}
override fun scrollToStart(): Boolean {
val result = super.scrollToStart()
if (result) {

View File

@ -2,12 +2,11 @@ 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.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.mariotaku.ktextension.toString
import org.mariotaku.restfu.http.HttpRequest
import org.mariotaku.twidere.extension.atto.firstElementOrNull
import org.mariotaku.twidere.model.event.StatusListChangedEvent
import org.mariotaku.twidere.view.LinkPreviewData
class LinkPreviewTask(
@ -19,19 +18,19 @@ class LinkPreviewTask(
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)
val response = runCatching {
restHttpClient.newCall(
HttpRequest
.Builder()
.url(url.replace("http:", "https:"))
.method("GET")
.build()
).execute()
}.getOrNull()
return response?.body?.stream()?.toString(charset = Charsets.UTF_8, close = true)?.let {
Jsoup.parse(it)
}?.let { doc ->
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 {

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -65,6 +65,8 @@ interface IStatusViewHolder : CardMediaContainer.OnMediaClickListener {
fun onUserProfileClick(holder: IStatusViewHolder, position: Int) {}
fun onFilterClick(holder: TimelineFilterHeaderViewHolder) {}
fun onLinkClick(holder: IStatusViewHolder, position: Int) {}
}
}

View File

@ -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)

View File

@ -290,11 +290,18 @@
</FrameLayout>
<org.mariotaku.twidere.view.LinkPreviewView
android:id="@+id/linkPreview"
android:layout_margin="@dimen/element_spacing_normal"
android:layout_below="@+id/mediaPreviewContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<org.mariotaku.twidere.view.TwitterCardContainer
android:id="@+id/twitterCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/mediaPreviewContainer"
android:layout_below="@+id/linkPreview"
android:visibility="gone"/>
<org.mariotaku.twidere.view.ColorLabelLinearLayout

View File

@ -1,25 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<merge
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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<TextView
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
tools:text="Google"
android:id="@+id/link_preview_title"
<LinearLayout
android:orientation="vertical"
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>
android:layout_height="wrap_content">
<ImageView
tools:src="@mipmap/ic_launcher"
android:id="@+id/link_preview_img"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:paddingRight="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:paddingTop="8dp"
style="@style/TextAppearance.MaterialComponents.Body1"
tools:text="Google"
android:id="@+id/link_preview_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:paddingRight="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:paddingBottom="8dp"
android:singleLine="true"
android:maxLines="1"
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
android:visibility="gone"
android:id="@+id/link_preview_loader"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</merge>

View File

@ -212,6 +212,7 @@
<org.mariotaku.twidere.view.LinkPreviewView
android:id="@+id/linkPreview"
android:layout_marginTop="@dimen/element_spacing_normal"
android:layout_below="@+id/mediaPreview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
@ -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 @@
<include layout="@layout/layout_card_media_preview"/>
</org.mariotaku.twidere.view.CardMediaContainer>
</org.mariotaku.twidere.view.ColorLabelRelativeLayout>
</RelativeLayout>

View File

@ -446,6 +446,7 @@
<string name="hide_card_actions">Hide card actions</string>
<string name="hide_card_numbers">Hide card numbers</string>
<string name="show_link_preview">Show link preview</string>
<string name="hide_quotes">Hide quotes</string>
<string name="hide_replies">Hide replies</string>
<string name="hide_retweets">Hide retweets</string>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">fanfou.com</domain>
</domain-config>
<base-config cleartextTrafficPermitted="true">
</base-config>
</network-security-config>

View File

@ -111,8 +111,18 @@
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="i_want_my_stars_back"
android:key="show_link_preview"
android:order="35"
android:title="@string/show_link_preview">
<extra
android:name="should_recreate"
android:value="true"/>
</SwitchPreferenceCompat>
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="i_want_my_stars_back"
android:order="36"
android:summary="@string/i_want_my_stars_back_summary"
android:title="@string/i_want_my_stars_back">
<extra
@ -123,7 +133,7 @@
<org.mariotaku.twidere.preference.FavoriteConfirmSwitchPreference
android:defaultValue="false"
android:key="favorite_confirmation"
android:order="36"
android:order="37"
android:summary="@string/preference_summary_favorite_confirmation"
android:title="@string/preference_title_favorite_confirmation"/>