Convert AccountViewHolder from Java to Kotlin (#3044)

* Convert AccountViewHolder from Java to Kotlin

Use view binding in the converted code, which requires small changes in code
that calls constructors.

Pass showBotOverlays as a parameter, rather than having the code reach in to
the shared preferences, fixing a layering violation. This affects callers
and classes derived from AccountAdapter.

* Use 2-arg getString

* Simplify setting bot badge indicator

- Specify the drawable in the XML
- Use visible() to set visibility
- Rename ID to account_bot_badge to make it clearer that this is all it is for

* Use lateinit to avoid needing !! later
This commit is contained in:
Nik Clayton 2022-12-28 19:07:43 +01:00 committed by GitHub
parent a21f2fadf9
commit ee765a3117
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 116 additions and 91 deletions

View File

@ -26,7 +26,8 @@ import com.keylesspalace.tusky.util.removeDuplicates
abstract class AccountAdapter<AVH : RecyclerView.ViewHolder> internal constructor( abstract class AccountAdapter<AVH : RecyclerView.ViewHolder> internal constructor(
var accountActionListener: AccountActionListener, var accountActionListener: AccountActionListener,
protected val animateAvatar: Boolean, protected val animateAvatar: Boolean,
protected val animateEmojis: Boolean protected val animateEmojis: Boolean,
protected val showBotOverlay: Boolean
) : RecyclerView.Adapter<RecyclerView.ViewHolder?>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder?>() {
var accountList = mutableListOf<TimelineAccount>() var accountList = mutableListOf<TimelineAccount>()
private var bottomLoading: Boolean = false private var bottomLoading: Boolean = false

View File

@ -1,61 +0,0 @@
package com.keylesspalace.tusky.adapter;
import android.content.SharedPreferences;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.TimelineAccount;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.interfaces.LinkListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
public class AccountViewHolder extends RecyclerView.ViewHolder {
private TextView username;
private TextView displayName;
private ImageView avatar;
private ImageView avatarInset;
private String accountId;
private boolean showBotOverlay;
public AccountViewHolder(View itemView) {
super(itemView);
username = itemView.findViewById(R.id.account_username);
displayName = itemView.findViewById(R.id.account_display_name);
avatar = itemView.findViewById(R.id.account_avatar);
avatarInset = itemView.findViewById(R.id.account_avatar_inset);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(itemView.getContext());
showBotOverlay = sharedPrefs.getBoolean("showBotOverlay", true);
}
public void setupWithAccount(TimelineAccount account, boolean animateAvatar, boolean animateEmojis) {
accountId = account.getId();
String format = username.getContext().getString(R.string.post_username_format);
String formattedUsername = String.format(format, account.getUsername());
username.setText(formattedUsername);
CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis);
displayName.setText(emojifiedName);
int avatarRadius = avatar.getContext().getResources()
.getDimensionPixelSize(R.dimen.avatar_radius_48dp);
ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
if (showBotOverlay && account.getBot()) {
avatarInset.setVisibility(View.VISIBLE);
avatarInset.setImageResource(R.drawable.bot_badge);
} else {
avatarInset.setVisibility(View.GONE);
}
}
void setupActionListener(final AccountActionListener listener) {
itemView.setOnClickListener(v -> listener.onViewAccount(accountId));
}
public void setupLinkListener(final LinkListener listener) {
itemView.setOnClickListener(v -> listener.onViewAccount(accountId));
}
}

View File

@ -0,0 +1,56 @@
package com.keylesspalace.tusky.adapter
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAccountBinding
import com.keylesspalace.tusky.entity.TimelineAccount
import com.keylesspalace.tusky.interfaces.AccountActionListener
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.visible
class AccountViewHolder(
private val binding: ItemAccountBinding
) : RecyclerView.ViewHolder(binding.root) {
private lateinit var accountId: String
fun setupWithAccount(
account: TimelineAccount,
animateAvatar: Boolean,
animateEmojis: Boolean,
showBotOverlay: Boolean
) {
accountId = account.id
binding.accountUsername.text = binding.accountUsername.context.getString(
R.string.post_username_format,
account.username
)
val emojifiedName = account.name.emojify(
account.emojis,
binding.accountDisplayName,
animateEmojis
)
binding.accountDisplayName.text = emojifiedName
val avatarRadius = binding.accountAvatar.context.resources
.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
loadAvatar(account.avatar, binding.accountAvatar, avatarRadius, animateAvatar)
binding.accountBotBadge.visible(showBotOverlay && account.bot)
}
fun setupActionListener(listener: AccountActionListener) {
itemView.setOnClickListener { listener.onViewAccount(accountId) }
}
fun setupLinkListener(listener: LinkListener) {
itemView.setOnClickListener {
listener.onViewAccount(
accountId
)
}
}
}

View File

@ -31,11 +31,13 @@ import com.keylesspalace.tusky.util.loadAvatar
class BlocksAdapter( class BlocksAdapter(
accountActionListener: AccountActionListener, accountActionListener: AccountActionListener,
animateAvatar: Boolean, animateAvatar: Boolean,
animateEmojis: Boolean animateEmojis: Boolean,
showBotOverlay: Boolean,
) : AccountAdapter<BlocksAdapter.BlockedUserViewHolder>( ) : AccountAdapter<BlocksAdapter.BlockedUserViewHolder>(
accountActionListener, accountActionListener,
animateAvatar, animateAvatar,
animateEmojis animateEmojis,
showBotOverlay
) { ) {
override fun createAccountViewHolder(parent: ViewGroup): BlockedUserViewHolder { override fun createAccountViewHolder(parent: ViewGroup): BlockedUserViewHolder {
val view = LayoutInflater.from(parent.context) val view = LayoutInflater.from(parent.context)

View File

@ -16,23 +16,37 @@ package com.keylesspalace.tusky.adapter
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.ItemAccountBinding
import com.keylesspalace.tusky.interfaces.AccountActionListener import com.keylesspalace.tusky.interfaces.AccountActionListener
/** Displays either a follows or following list. */ /** Displays either a follows or following list. */
class FollowAdapter( class FollowAdapter(
accountActionListener: AccountActionListener, accountActionListener: AccountActionListener,
animateAvatar: Boolean, animateAvatar: Boolean,
animateEmojis: Boolean animateEmojis: Boolean,
) : AccountAdapter<AccountViewHolder>(accountActionListener, animateAvatar, animateEmojis) { showBotOverlay: Boolean
) : AccountAdapter<AccountViewHolder>(
accountActionListener,
animateAvatar,
animateEmojis,
showBotOverlay
) {
override fun createAccountViewHolder(parent: ViewGroup): AccountViewHolder { override fun createAccountViewHolder(parent: ViewGroup): AccountViewHolder {
val view = LayoutInflater.from(parent.context) val binding = ItemAccountBinding.inflate(
.inflate(R.layout.item_account, parent, false) LayoutInflater.from(parent.context),
return AccountViewHolder(view) parent,
false
)
return AccountViewHolder(binding)
} }
override fun onBindAccountViewHolder(viewHolder: AccountViewHolder, position: Int) { override fun onBindAccountViewHolder(viewHolder: AccountViewHolder, position: Int) {
viewHolder.setupWithAccount(accountList[position], animateAvatar, animateEmojis) viewHolder.setupWithAccount(
accountList[position],
animateAvatar,
animateEmojis,
showBotOverlay
)
viewHolder.setupActionListener(accountActionListener) viewHolder.setupActionListener(accountActionListener)
} }
} }

View File

@ -34,7 +34,12 @@ class FollowRequestViewHolder(
private val showHeader: Boolean private val showHeader: Boolean
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun setupWithAccount(account: TimelineAccount, animateAvatar: Boolean, animateEmojis: Boolean) { fun setupWithAccount(
account: TimelineAccount,
animateAvatar: Boolean,
animateEmojis: Boolean,
showBotOverlay: Boolean
) {
val wrappedName = account.name.unicodeWrap() val wrappedName = account.name.unicodeWrap()
val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView, animateEmojis) val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView, animateEmojis)
binding.displayNameTextView.text = emojifiedName binding.displayNameTextView.text = emojifiedName

View File

@ -23,8 +23,9 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener
class FollowRequestsAdapter( class FollowRequestsAdapter(
accountActionListener: AccountActionListener, accountActionListener: AccountActionListener,
animateAvatar: Boolean, animateAvatar: Boolean,
animateEmojis: Boolean animateEmojis: Boolean,
) : AccountAdapter<FollowRequestViewHolder>(accountActionListener, animateAvatar, animateEmojis) { showBotOverlay: Boolean
) : AccountAdapter<FollowRequestViewHolder>(accountActionListener, animateAvatar, animateEmojis, showBotOverlay) {
override fun createAccountViewHolder(parent: ViewGroup): FollowRequestViewHolder { override fun createAccountViewHolder(parent: ViewGroup): FollowRequestViewHolder {
val binding = ItemFollowRequestBinding.inflate( val binding = ItemFollowRequestBinding.inflate(
LayoutInflater.from(parent.context), parent, false LayoutInflater.from(parent.context), parent, false
@ -33,7 +34,7 @@ class FollowRequestsAdapter(
} }
override fun onBindAccountViewHolder(viewHolder: FollowRequestViewHolder, position: Int) { override fun onBindAccountViewHolder(viewHolder: FollowRequestViewHolder, position: Int) {
viewHolder.setupWithAccount(accountList[position], animateAvatar, animateEmojis) viewHolder.setupWithAccount(accountList[position], animateAvatar, animateEmojis, showBotOverlay)
viewHolder.setupActionListener(accountActionListener, accountList[position].id) viewHolder.setupActionListener(accountActionListener, accountList[position].id)
} }
} }

View File

@ -13,7 +13,6 @@ import com.keylesspalace.tusky.entity.TimelineAccount
import com.keylesspalace.tusky.interfaces.AccountActionListener import com.keylesspalace.tusky.interfaces.AccountActionListener
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 java.util.HashMap
/** /**
* Displays a list of muted accounts with mute/unmute account and mute/unmute notifications * Displays a list of muted accounts with mute/unmute account and mute/unmute notifications
@ -22,11 +21,13 @@ import java.util.HashMap
class MutesAdapter( class MutesAdapter(
accountActionListener: AccountActionListener, accountActionListener: AccountActionListener,
animateAvatar: Boolean, animateAvatar: Boolean,
animateEmojis: Boolean animateEmojis: Boolean,
showBotOverlay: Boolean
) : AccountAdapter<MutesAdapter.MutedUserViewHolder>( ) : AccountAdapter<MutesAdapter.MutedUserViewHolder>(
accountActionListener, accountActionListener,
animateAvatar, animateAvatar,
animateEmojis animateEmojis,
showBotOverlay
) { ) {
private val mutingNotificationsMap = HashMap<String, Boolean>() private val mutingNotificationsMap = HashMap<String, Boolean>()

View File

@ -253,7 +253,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
case VIEW_TYPE_FOLLOW_REQUEST: { case VIEW_TYPE_FOLLOW_REQUEST: {
if (payloadForHolder == null) { if (payloadForHolder == null) {
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
holder.setupWithAccount(concreteNotification.getAccount(), statusDisplayOptions.animateAvatars(), statusDisplayOptions.animateEmojis()); holder.setupWithAccount(concreteNotification.getAccount(), statusDisplayOptions.animateAvatars(), statusDisplayOptions.animateEmojis(), statusDisplayOptions.showBotOverlay());
holder.setupActionListener(accountActionListener, concreteNotification.getAccount().getId()); holder.setupActionListener(accountActionListener, concreteNotification.getAccount().getId());
} }
break; break;

View File

@ -19,24 +19,27 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.AccountViewHolder import com.keylesspalace.tusky.adapter.AccountViewHolder
import com.keylesspalace.tusky.databinding.ItemAccountBinding
import com.keylesspalace.tusky.entity.TimelineAccount import com.keylesspalace.tusky.entity.TimelineAccount
import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.interfaces.LinkListener
class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean) : class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean, private val showBotOverlay: Boolean) :
PagingDataAdapter<TimelineAccount, AccountViewHolder>(ACCOUNT_COMPARATOR) { PagingDataAdapter<TimelineAccount, AccountViewHolder>(ACCOUNT_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder {
val view = LayoutInflater.from(parent.context) val binding = ItemAccountBinding.inflate(
.inflate(R.layout.item_account, parent, false) LayoutInflater.from(parent.context),
return AccountViewHolder(view) parent,
false
)
return AccountViewHolder(binding)
} }
override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { override fun onBindViewHolder(holder: AccountViewHolder, position: Int) {
getItem(position)?.let { item -> getItem(position)?.let { item ->
holder.apply { holder.apply {
setupWithAccount(item, animateAvatars, animateEmojis) setupWithAccount(item, animateAvatars, animateEmojis, showBotOverlay)
setupLinkListener(linkListener) setupLinkListener(linkListener)
} }
} }

View File

@ -30,7 +30,8 @@ class SearchAccountsFragment : SearchFragment<TimelineAccount>() {
return SearchAccountsAdapter( return SearchAccountsAdapter(
this, this,
preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
preferences.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true)
) )
} }

View File

@ -95,17 +95,18 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
val pm = PreferenceManager.getDefaultSharedPreferences(view.context) val pm = PreferenceManager.getDefaultSharedPreferences(view.context)
val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false) val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
val showBotOverlay = pm.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true)
adapter = when (type) { adapter = when (type) {
Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis) Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis, showBotOverlay)
Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis) Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis, showBotOverlay)
Type.FOLLOW_REQUESTS -> { Type.FOLLOW_REQUESTS -> {
val headerAdapter = FollowRequestsHeaderAdapter(accountManager.activeAccount!!.domain, arguments?.getBoolean(ARG_ACCOUNT_LOCKED) == true) val headerAdapter = FollowRequestsHeaderAdapter(accountManager.activeAccount!!.domain, arguments?.getBoolean(ARG_ACCOUNT_LOCKED) == true)
val followRequestsAdapter = FollowRequestsAdapter(this, animateAvatar, animateEmojis) val followRequestsAdapter = FollowRequestsAdapter(this, animateAvatar, animateEmojis, showBotOverlay)
binding.recyclerView.adapter = ConcatAdapter(headerAdapter, followRequestsAdapter) binding.recyclerView.adapter = ConcatAdapter(headerAdapter, followRequestsAdapter)
followRequestsAdapter followRequestsAdapter
} }
else -> FollowAdapter(this, animateAvatar, animateEmojis) else -> FollowAdapter(this, animateAvatar, animateEmojis, showBotOverlay)
} }
if (binding.recyclerView.adapter == null) { if (binding.recyclerView.adapter == null) {
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter

View File

@ -21,12 +21,13 @@
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
<ImageView <ImageView
android:id="@+id/account_avatar_inset" android:id="@+id/account_bot_badge"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:contentDescription="@null" android:contentDescription="@null"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:visibility="gone" android:visibility="gone"
android:src="@drawable/bot_badge"
app:layout_constraintBottom_toBottomOf="@id/account_avatar" app:layout_constraintBottom_toBottomOf="@id/account_avatar"
app:layout_constraintEnd_toEndOf="@id/account_avatar" app:layout_constraintEnd_toEndOf="@id/account_avatar"
tools:src="#000" tools:src="#000"