Use tags from status when adding handlers to hashtag spans in status content (#2344)
* Migrate LinkHelper to kotlin * Support tags field on statuses * Use embedded tags list in status instead of text scraping to embed tag click handler. Fixes #2283 * Make mentions and tags lists nonnullable * Make LinkHelper.openLink a Context extension method * Use builtin extension for uri conversion * More cleanup in LinkHelper * Add tests for LinkHelper.getDomain * Unbreak tags in places that don't have a tag list (e.g. profiles) * Fixup javadoc
This commit is contained in:
parent
f822234995
commit
addce87eb6
34 changed files with 1294 additions and 296 deletions
|
|
@ -71,13 +71,15 @@ import com.keylesspalace.tusky.interfaces.ReselectableFragment
|
|||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.DefaultTextWatcher
|
||||
import com.keylesspalace.tusky.util.Error
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.Loading
|
||||
import com.keylesspalace.tusky.util.Success
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.getDomain
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.loadAvatar
|
||||
import com.keylesspalace.tusky.util.openLink
|
||||
import com.keylesspalace.tusky.util.setClickableText
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
|
|
@ -409,7 +411,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
binding.accountDisplayNameTextView.text = account.name.emojify(account.emojis, binding.accountDisplayNameTextView, animateEmojis)
|
||||
|
||||
val emojifiedNote = account.note.emojify(account.emojis, binding.accountNoteTextView, animateEmojis)
|
||||
LinkHelper.setClickableText(binding.accountNoteTextView, emojifiedNote, null, this)
|
||||
setClickableText(binding.accountNoteTextView, emojifiedNote, emptyList(), null, this)
|
||||
|
||||
// accountFieldAdapter.fields = account.fields ?: emptyList()
|
||||
accountFieldAdapter.emojis = account.emojis ?: emptyList()
|
||||
|
|
@ -517,7 +519,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
if (account.isRemote()) {
|
||||
binding.accountRemoveView.show()
|
||||
binding.accountRemoveView.setOnClickListener {
|
||||
LinkHelper.openLink(account.url, this)
|
||||
openLink(account.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -714,7 +716,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
if (loadedAccount != null) {
|
||||
val muteDomain = menu.findItem(R.id.action_mute_domain)
|
||||
domain = LinkHelper.getDomain(loadedAccount?.url)
|
||||
domain = getDomain(loadedAccount?.url)
|
||||
if (domain.isEmpty()) {
|
||||
// If we can't get the domain, there's no way we can mute it anyway...
|
||||
menu.removeItem(R.id.action_mute_domain)
|
||||
|
|
@ -834,8 +836,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
when (item.itemId) {
|
||||
R.id.action_open_in_web -> {
|
||||
// If the account isn't loaded yet, eat the input.
|
||||
if (loadedAccount != null) {
|
||||
LinkHelper.openLink(loadedAccount?.url, this)
|
||||
if (loadedAccount?.url != null) {
|
||||
openLink(loadedAccount!!.url)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ import com.keylesspalace.tusky.entity.IdentityProof
|
|||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.Either
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.createClickableText
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.setClickableText
|
||||
|
||||
class AccountFieldAdapter(
|
||||
private val linkListener: LinkListener,
|
||||
|
|
@ -54,7 +55,7 @@ class AccountFieldAdapter(
|
|||
val identityProof = proofOrField.asLeft()
|
||||
|
||||
nameTextView.text = identityProof.provider
|
||||
valueTextView.text = LinkHelper.createClickableText(identityProof.username, identityProof.profileUrl)
|
||||
valueTextView.text = createClickableText(identityProof.username, identityProof.profileUrl)
|
||||
|
||||
valueTextView.movementMethod = LinkMovementMethod.getInstance()
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ class AccountFieldAdapter(
|
|||
nameTextView.text = emojifiedName
|
||||
|
||||
val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis)
|
||||
LinkHelper.setClickableText(valueTextView, emojifiedValue, null, linkListener)
|
||||
setClickableText(valueTextView, emojifiedValue, emptyList(), null, linkListener)
|
||||
|
||||
if (field.verifiedAt != null) {
|
||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ import com.keylesspalace.tusky.entity.Attachment
|
|||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.interfaces.RefreshableFragment
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.openLink
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.view.SquareImageView
|
||||
|
|
@ -252,7 +252,7 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
|||
}
|
||||
}
|
||||
Attachment.Type.UNKNOWN -> {
|
||||
LinkHelper.openLink(items[currentIndex].attachment.url, context)
|
||||
context?.openLink(items[currentIndex].attachment.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ import com.keylesspalace.tusky.entity.Announcement
|
|||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.EmojiSpan
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.setClickableText
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
interface AnnouncementActionListener : LinkListener {
|
||||
|
|
@ -62,7 +62,7 @@ class AnnouncementAdapter(
|
|||
|
||||
val emojifiedText: CharSequence = item.content.emojify(item.emojis, text, animateEmojis)
|
||||
|
||||
LinkHelper.setClickableText(text, emojifiedText, item.mentions, listener)
|
||||
setClickableText(text, emojifiedText, item.mentions, item.tags, listener)
|
||||
|
||||
// If wellbeing mode is enabled, announcement badge counts should not be shown.
|
||||
if (wellbeingEnabled) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.keylesspalace.tusky.entity.Account
|
|||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Conversation
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.HashTag
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.util.shouldTrimStatus
|
||||
|
|
@ -79,6 +80,7 @@ data class ConversationStatusEntity(
|
|||
val spoilerText: String,
|
||||
val attachments: ArrayList<Attachment>,
|
||||
val mentions: List<Status.Mention>,
|
||||
val tags: List<HashTag>,
|
||||
val showingHiddenContent: Boolean,
|
||||
val expanded: Boolean,
|
||||
val collapsible: Boolean,
|
||||
|
|
@ -107,6 +109,7 @@ data class ConversationStatusEntity(
|
|||
if (spoilerText != other.spoilerText) return false
|
||||
if (attachments != other.attachments) return false
|
||||
if (mentions != other.mentions) return false
|
||||
if (tags != other.tags) return false
|
||||
if (showingHiddenContent != other.showingHiddenContent) return false
|
||||
if (expanded != other.expanded) return false
|
||||
if (collapsible != other.collapsible) return false
|
||||
|
|
@ -132,6 +135,7 @@ data class ConversationStatusEntity(
|
|||
result = 31 * result + spoilerText.hashCode()
|
||||
result = 31 * result + attachments.hashCode()
|
||||
result = 31 * result + mentions.hashCode()
|
||||
result = 31 * result + tags.hashCode()
|
||||
result = 31 * result + showingHiddenContent.hashCode()
|
||||
result = 31 * result + expanded.hashCode()
|
||||
result = 31 * result + collapsible.hashCode()
|
||||
|
|
@ -162,6 +166,7 @@ data class ConversationStatusEntity(
|
|||
visibility = Status.Visibility.DIRECT,
|
||||
attachments = attachments,
|
||||
mentions = mentions,
|
||||
tags = tags,
|
||||
application = null,
|
||||
pinned = false,
|
||||
muted = muted,
|
||||
|
|
@ -197,6 +202,7 @@ fun Status.toEntity() =
|
|||
spoilerText = spoilerText,
|
||||
attachments = attachments,
|
||||
mentions = mentions,
|
||||
tags = tags,
|
||||
showingHiddenContent = false,
|
||||
expanded = false,
|
||||
collapsible = shouldTrimStatus(content),
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
statusDisplayOptions);
|
||||
|
||||
setSpoilerAndContent(status.getExpanded(), status.getContent(), status.getSpoilerText(),
|
||||
status.getMentions(), status.getEmojis(),
|
||||
status.getMentions(), status.getTags(), status.getEmojis(),
|
||||
PollViewDataKt.toViewData(status.getPoll()), statusDisplayOptions, listener);
|
||||
|
||||
setConversationName(conversation.getAccounts());
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.components.report.model.StatusViewState
|
||||
import com.keylesspalace.tusky.databinding.ItemReportStatusBinding
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.HashTag
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.util.StatusViewHelper
|
||||
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.COLLAPSE_INPUT_FILTER
|
||||
|
|
@ -33,6 +33,8 @@ import com.keylesspalace.tusky.util.StatusViewHelper.Companion.NO_INPUT_FILTER
|
|||
import com.keylesspalace.tusky.util.TimestampUtils
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.setClickableMentions
|
||||
import com.keylesspalace.tusky.util.setClickableText
|
||||
import com.keylesspalace.tusky.util.shouldTrimStatus
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.viewdata.toViewData
|
||||
|
|
@ -96,7 +98,7 @@ class StatusViewHolder(
|
|||
)
|
||||
|
||||
if (status.spoilerText.isBlank()) {
|
||||
setTextVisible(true, status.content, status.mentions, status.emojis, adapterHandler)
|
||||
setTextVisible(true, status.content, status.mentions, status.tags, status.emojis, adapterHandler)
|
||||
binding.statusContentWarningButton.hide()
|
||||
binding.statusContentWarningDescription.hide()
|
||||
} else {
|
||||
|
|
@ -110,11 +112,11 @@ class StatusViewHolder(
|
|||
val contentShown = viewState.isContentShow(status.id, true)
|
||||
binding.statusContentWarningDescription.invalidate()
|
||||
viewState.setContentShow(status.id, !contentShown)
|
||||
setTextVisible(!contentShown, status.content, status.mentions, status.emojis, adapterHandler)
|
||||
setTextVisible(!contentShown, status.content, status.mentions, status.tags, status.emojis, adapterHandler)
|
||||
setContentWarningButtonText(!contentShown)
|
||||
}
|
||||
}
|
||||
setTextVisible(viewState.isContentShow(status.id, true), status.content, status.mentions, status.emojis, adapterHandler)
|
||||
setTextVisible(viewState.isContentShow(status.id, true), status.content, status.mentions, status.tags, status.emojis, adapterHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,15 +132,16 @@ class StatusViewHolder(
|
|||
private fun setTextVisible(
|
||||
expanded: Boolean,
|
||||
content: Spanned,
|
||||
mentions: List<Status.Mention>?,
|
||||
mentions: List<Status.Mention>,
|
||||
tags: List<HashTag>?,
|
||||
emojis: List<Emoji>,
|
||||
listener: LinkListener
|
||||
) {
|
||||
if (expanded) {
|
||||
val emojifiedText = content.emojify(emojis, binding.statusContent, statusDisplayOptions.animateEmojis)
|
||||
LinkHelper.setClickableText(binding.statusContent, emojifiedText, mentions, listener)
|
||||
setClickableText(binding.statusContent, emojifiedText, mentions, tags, listener)
|
||||
} else {
|
||||
LinkHelper.setClickableMentions(binding.statusContent, mentions, listener)
|
||||
setClickableMentions(binding.statusContent, mentions, listener)
|
||||
}
|
||||
if (binding.statusContent.text.isNullOrBlank()) {
|
||||
binding.statusContent.hide()
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ import com.keylesspalace.tusky.interfaces.AccountSelectionListener
|
|||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.CardViewMode
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.util.openLink
|
||||
import com.keylesspalace.tusky.view.showMuteAccountDialog
|
||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
|
|
@ -142,7 +142,7 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
}
|
||||
}
|
||||
Attachment.Type.UNKNOWN -> {
|
||||
LinkHelper.openLink(actionable.attachments[attachmentIndex].url, context)
|
||||
context?.openLink(actionable.attachments[attachmentIndex].url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.keylesspalace.tusky.db.TimelineStatusWithAccount
|
|||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.HashTag
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.util.shouldTrimStatus
|
||||
|
|
@ -41,6 +42,7 @@ data class Placeholder(
|
|||
private val attachmentArrayListType = object : TypeToken<ArrayList<Attachment>>() {}.type
|
||||
private val emojisListType = object : TypeToken<List<Emoji>>() {}.type
|
||||
private val mentionListType = object : TypeToken<List<Status.Mention>>() {}.type
|
||||
private val tagListType = object : TypeToken<List<HashTag>>() {}.type
|
||||
|
||||
fun Account.toEntity(accountId: Long, gson: Gson): TimelineAccountEntity {
|
||||
return TimelineAccountEntity(
|
||||
|
|
@ -99,6 +101,7 @@ fun Placeholder.toEntity(timelineUserId: Long): TimelineStatusEntity {
|
|||
visibility = Status.Visibility.UNKNOWN,
|
||||
attachments = null,
|
||||
mentions = null,
|
||||
tags = null,
|
||||
application = null,
|
||||
reblogServerId = null,
|
||||
reblogAccountId = null,
|
||||
|
|
@ -138,6 +141,7 @@ fun Status.toEntity(
|
|||
visibility = actionableStatus.visibility,
|
||||
attachments = actionableStatus.attachments.let(gson::toJson),
|
||||
mentions = actionableStatus.mentions.let(gson::toJson),
|
||||
tags = actionableStatus.tags.let(gson::toJson),
|
||||
application = actionableStatus.application.let(gson::toJson),
|
||||
reblogServerId = reblog?.id,
|
||||
reblogAccountId = reblog?.let { this.account.id },
|
||||
|
|
@ -157,6 +161,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
|
||||
val attachments: ArrayList<Attachment> = gson.fromJson(status.attachments, attachmentArrayListType) ?: arrayListOf()
|
||||
val mentions: List<Status.Mention> = gson.fromJson(status.mentions, mentionListType) ?: emptyList()
|
||||
val tags: List<HashTag> = gson.fromJson(status.tags, tagListType) ?: emptyList()
|
||||
val application = gson.fromJson(status.application, Status.Application::class.java)
|
||||
val emojis: List<Emoji> = gson.fromJson(status.emojis, emojisListType) ?: emptyList()
|
||||
val poll: Poll? = gson.fromJson(status.poll, Poll::class.java)
|
||||
|
|
@ -183,6 +188,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
visibility = status.visibility,
|
||||
attachments = attachments,
|
||||
mentions = mentions,
|
||||
tags = tags,
|
||||
application = application,
|
||||
pinned = false,
|
||||
muted = status.muted,
|
||||
|
|
@ -211,6 +217,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
visibility = status.visibility,
|
||||
attachments = ArrayList(),
|
||||
mentions = listOf(),
|
||||
tags = listOf(),
|
||||
application = null,
|
||||
pinned = status.pinned,
|
||||
muted = status.muted,
|
||||
|
|
@ -239,6 +246,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
visibility = status.visibility,
|
||||
attachments = attachments,
|
||||
mentions = mentions,
|
||||
tags = tags,
|
||||
application = application,
|
||||
pinned = status.pinned,
|
||||
muted = status.muted,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import com.keylesspalace.tusky.entity.Status
|
|||
import com.keylesspalace.tusky.network.FilterModel
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.network.TimelineCases
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.getDomain
|
||||
import com.keylesspalace.tusky.util.inc
|
||||
import com.keylesspalace.tusky.util.toViewData
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
|
|
@ -117,7 +117,7 @@ class NetworkTimelineViewModel @Inject constructor(
|
|||
override fun removeAllByInstance(instance: String) {
|
||||
statusData.removeAll { vd ->
|
||||
val status = vd.asStatusOrNull()?.status ?: return@removeAll false
|
||||
LinkHelper.getDomain(status.account.url) == instance
|
||||
getDomain(status.account.url) == instance
|
||||
}
|
||||
currentSource?.invalidate()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue