Make links in bios of follow request and follow notifications work (#3646)

* make links in bios of follow request and follow notifications work
* use ClickableSpanTextView to display account notes
This commit is contained in:
Konrad Pozniak 2023-05-13 15:32:56 +02:00 committed by GitHub
parent ad9e57cb10
commit 74a00c0591
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 47 additions and 13 deletions

View file

@ -25,11 +25,14 @@ import com.keylesspalace.tusky.components.notifications.NotificationsPagingAdapt
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
import com.keylesspalace.tusky.entity.TimelineAccount import com.keylesspalace.tusky.entity.TimelineAccount
import com.keylesspalace.tusky.interfaces.AccountActionListener import com.keylesspalace.tusky.interfaces.AccountActionListener
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.loadAvatar import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.parseAsMastodonHtml import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.setClickableText
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.unicodeWrap import com.keylesspalace.tusky.util.unicodeWrap
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.viewdata.NotificationViewData import com.keylesspalace.tusky.viewdata.NotificationViewData
@ -37,6 +40,7 @@ import com.keylesspalace.tusky.viewdata.NotificationViewData
class FollowRequestViewHolder( class FollowRequestViewHolder(
private val binding: ItemFollowRequestBinding, private val binding: ItemFollowRequestBinding,
private val accountActionListener: AccountActionListener, private val accountActionListener: AccountActionListener,
private val linkListener: LinkListener,
private val showHeader: Boolean private val showHeader: Boolean
) : NotificationsPagingAdapter.ViewHolder, RecyclerView.ViewHolder(binding.root) { ) : NotificationsPagingAdapter.ViewHolder, RecyclerView.ViewHolder(binding.root) {
@ -92,9 +96,11 @@ class FollowRequestViewHolder(
if (account.note.isEmpty()) { if (account.note.isEmpty()) {
binding.accountNote.hide() binding.accountNote.hide()
} else { } else {
binding.accountNote.text = binding.accountNote.show()
account.note.parseAsMastodonHtml()
.emojify(account.emojis, binding.accountNote, animateEmojis) val emojifiedNote = account.note.parseAsMastodonHtml()
.emojify(account.emojis, binding.accountNote, animateEmojis)
setClickableText(binding.accountNote, emojifiedNote, emptyList(), null, linkListener)
} }
val avatarRadius = binding.avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp) val avatarRadius = binding.avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
loadAvatar(account.avatar, binding.avatar, avatarRadius, animateAvatar) loadAvatar(account.avatar, binding.avatar, avatarRadius, animateAvatar)

View file

@ -19,14 +19,14 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.commit import androidx.fragment.app.commit
import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.BottomSheetActivity
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ActivityAccountListBinding import com.keylesspalace.tusky.databinding.ActivityAccountListBinding
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import javax.inject.Inject import javax.inject.Inject
class AccountListActivity : BaseActivity(), HasAndroidInjector { class AccountListActivity : BottomSheetActivity(), HasAndroidInjector {
@Inject @Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any> lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>

View file

@ -32,7 +32,10 @@ import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from
import autodispose2.autoDispose import autodispose2.autoDispose
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.BottomSheetActivity
import com.keylesspalace.tusky.PostLookupFallbackBehavior
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.StatusListActivity
import com.keylesspalace.tusky.components.account.AccountActivity import com.keylesspalace.tusky.components.account.AccountActivity
import com.keylesspalace.tusky.components.accountlist.AccountListActivity.Type import com.keylesspalace.tusky.components.accountlist.AccountListActivity.Type
import com.keylesspalace.tusky.components.accountlist.adapter.AccountAdapter import com.keylesspalace.tusky.components.accountlist.adapter.AccountAdapter
@ -47,6 +50,7 @@ import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.Relationship import com.keylesspalace.tusky.entity.Relationship
import com.keylesspalace.tusky.entity.TimelineAccount import com.keylesspalace.tusky.entity.TimelineAccount
import com.keylesspalace.tusky.interfaces.AccountActionListener import com.keylesspalace.tusky.interfaces.AccountActionListener
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.HttpHeaderLink import com.keylesspalace.tusky.util.HttpHeaderLink
@ -60,7 +64,11 @@ import retrofit2.Response
import java.io.IOException import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountActionListener, Injectable { class AccountListFragment :
Fragment(R.layout.fragment_account_list),
AccountActionListener,
LinkListener,
Injectable {
@Inject @Inject
lateinit var api: MastodonApi lateinit var api: MastodonApi
@ -107,7 +115,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
instanceName = accountManager.activeAccount!!.domain, instanceName = accountManager.activeAccount!!.domain,
accountLocked = arguments?.getBoolean(ARG_ACCOUNT_LOCKED) == true accountLocked = arguments?.getBoolean(ARG_ACCOUNT_LOCKED) == true
) )
val followRequestsAdapter = FollowRequestsAdapter(this, animateAvatar, animateEmojis, showBotOverlay) val followRequestsAdapter = FollowRequestsAdapter(this, this, animateAvatar, animateEmojis, showBotOverlay)
binding.recyclerView.adapter = ConcatAdapter(headerAdapter, followRequestsAdapter) binding.recyclerView.adapter = ConcatAdapter(headerAdapter, followRequestsAdapter)
followRequestsAdapter followRequestsAdapter
} }
@ -131,6 +139,11 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
fetchAccounts() fetchAccounts()
} }
override fun onViewTag(tag: String) {
(activity as BaseActivity?)
?.startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag))
}
override fun onViewAccount(id: String) { override fun onViewAccount(id: String) {
(activity as BaseActivity?)?.let { (activity as BaseActivity?)?.let {
val intent = AccountActivity.getIntent(it, id) val intent = AccountActivity.getIntent(it, id)
@ -138,6 +151,10 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
} }
} }
override fun onViewUrl(url: String) {
(activity as BottomSheetActivity?)?.viewUrl(url, PostLookupFallbackBehavior.OPEN_IN_BROWSER)
}
override fun onMute(mute: Boolean, id: String, position: Int, notifications: Boolean) { override fun onMute(mute: Boolean, id: String, position: Int, notifications: Boolean) {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
try { try {

View file

@ -20,10 +20,12 @@ import android.view.ViewGroup
import com.keylesspalace.tusky.adapter.FollowRequestViewHolder import com.keylesspalace.tusky.adapter.FollowRequestViewHolder
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
import com.keylesspalace.tusky.interfaces.AccountActionListener import com.keylesspalace.tusky.interfaces.AccountActionListener
import com.keylesspalace.tusky.interfaces.LinkListener
/** Displays a list of follow requests with accept/reject buttons. */ /** Displays a list of follow requests with accept/reject buttons. */
class FollowRequestsAdapter( class FollowRequestsAdapter(
accountActionListener: AccountActionListener, accountActionListener: AccountActionListener,
private val linkListener: LinkListener,
animateAvatar: Boolean, animateAvatar: Boolean,
animateEmojis: Boolean, animateEmojis: Boolean,
showBotOverlay: Boolean showBotOverlay: Boolean
@ -43,6 +45,7 @@ class FollowRequestsAdapter(
return FollowRequestViewHolder( return FollowRequestViewHolder(
binding, binding,
accountActionListener, accountActionListener,
linkListener,
showHeader = false showHeader = false
) )
} }

View file

@ -22,16 +22,19 @@ import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemFollowBinding import com.keylesspalace.tusky.databinding.ItemFollowBinding
import com.keylesspalace.tusky.entity.Notification import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.entity.TimelineAccount import com.keylesspalace.tusky.entity.TimelineAccount
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.loadAvatar import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.parseAsMastodonHtml import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.setClickableText
import com.keylesspalace.tusky.util.unicodeWrap import com.keylesspalace.tusky.util.unicodeWrap
import com.keylesspalace.tusky.viewdata.NotificationViewData import com.keylesspalace.tusky.viewdata.NotificationViewData
class FollowViewHolder( class FollowViewHolder(
private val binding: ItemFollowBinding, private val binding: ItemFollowBinding,
private val notificationActionListener: NotificationActionListener private val notificationActionListener: NotificationActionListener,
private val linkListener: LinkListener
) : NotificationsPagingAdapter.ViewHolder, RecyclerView.ViewHolder(binding.root) { ) : NotificationsPagingAdapter.ViewHolder, RecyclerView.ViewHolder(binding.root) {
private val avatarRadius42dp = itemView.context.resources.getDimensionPixelSize( private val avatarRadius42dp = itemView.context.resources.getDimensionPixelSize(
R.dimen.avatar_radius_42dp R.dimen.avatar_radius_42dp
@ -94,11 +97,12 @@ class FollowViewHolder(
animateAvatars animateAvatars
) )
binding.notificationAccountNote.text = account.note.parseAsMastodonHtml().emojify( val emojifiedNote = account.note.parseAsMastodonHtml().emojify(
account.emojis, account.emojis,
binding.notificationAccountNote, binding.notificationAccountNote,
animateEmojis animateEmojis
) )
setClickableText(binding.notificationAccountNote, emojifiedNote, emptyList(), null, linkListener)
} }
private fun setupButtons(listener: NotificationActionListener, accountId: String) { private fun setupButtons(listener: NotificationActionListener, accountId: String) {

View file

@ -147,13 +147,15 @@ class NotificationsPagingAdapter(
NotificationViewKind.FOLLOW -> { NotificationViewKind.FOLLOW -> {
FollowViewHolder( FollowViewHolder(
ItemFollowBinding.inflate(inflater, parent, false), ItemFollowBinding.inflate(inflater, parent, false),
notificationActionListener notificationActionListener,
statusActionListener
) )
} }
NotificationViewKind.FOLLOW_REQUEST -> { NotificationViewKind.FOLLOW_REQUEST -> {
FollowRequestViewHolder( FollowRequestViewHolder(
ItemFollowRequestBinding.inflate(inflater, parent, false), ItemFollowRequestBinding.inflate(inflater, parent, false),
accountActionListener, accountActionListener,
statusActionListener,
showHeader = true showHeader = true
) )
} }

View file

@ -16,4 +16,6 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> <include layout="@layout/item_status_bottom_sheet" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -68,7 +68,7 @@
app:layout_constraintTop_toTopOf="@+id/notification_display_name" app:layout_constraintTop_toTopOf="@+id/notification_display_name"
tools:text="\@testuser" /> tools:text="\@testuser" />
<TextView <com.keylesspalace.tusky.view.ClickableSpanTextView
android:id="@+id/notification_account_note" android:id="@+id/notification_account_note"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -104,7 +104,7 @@
app:layout_constraintTop_toTopOf="@+id/rejectButton" app:layout_constraintTop_toTopOf="@+id/rejectButton"
app:srcCompat="@drawable/ic_check_24dp" /> app:srcCompat="@drawable/ic_check_24dp" />
<TextView <com.keylesspalace.tusky.view.ClickableSpanTextView
android:id="@+id/account_note" android:id="@+id/account_note"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"