From ee765a31175c29ac06cceb76b2a4d09755d520a5 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Wed, 28 Dec 2022 19:07:43 +0100 Subject: [PATCH] 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 --- .../tusky/adapter/AccountAdapter.kt | 3 +- .../tusky/adapter/AccountViewHolder.java | 61 ------------------- .../tusky/adapter/AccountViewHolder.kt | 56 +++++++++++++++++ .../tusky/adapter/BlocksAdapter.kt | 6 +- .../tusky/adapter/FollowAdapter.kt | 28 ++++++--- .../tusky/adapter/FollowRequestViewHolder.kt | 7 ++- .../tusky/adapter/FollowRequestsAdapter.kt | 7 ++- .../tusky/adapter/MutesAdapter.kt | 7 ++- .../tusky/adapter/NotificationsAdapter.java | 2 +- .../search/adapter/SearchAccountsAdapter.kt | 15 +++-- .../fragments/SearchAccountsFragment.kt | 3 +- .../tusky/fragment/AccountListFragment.kt | 9 +-- app/src/main/res/layout/item_account.xml | 3 +- 13 files changed, 116 insertions(+), 91 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.kt index 366dae7f9..bbd83df32 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.kt @@ -26,7 +26,8 @@ import com.keylesspalace.tusky.util.removeDuplicates abstract class AccountAdapter internal constructor( var accountActionListener: AccountActionListener, protected val animateAvatar: Boolean, - protected val animateEmojis: Boolean + protected val animateEmojis: Boolean, + protected val showBotOverlay: Boolean ) : RecyclerView.Adapter() { var accountList = mutableListOf() private var bottomLoading: Boolean = false diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java deleted file mode 100644 index 6672fff39..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java +++ /dev/null @@ -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)); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.kt new file mode 100644 index 000000000..f125422c3 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.kt @@ -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 + ) + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.kt index 859807e97..c4da2ab8c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.kt @@ -31,11 +31,13 @@ import com.keylesspalace.tusky.util.loadAvatar class BlocksAdapter( accountActionListener: AccountActionListener, animateAvatar: Boolean, - animateEmojis: Boolean + animateEmojis: Boolean, + showBotOverlay: Boolean, ) : AccountAdapter( accountActionListener, animateAvatar, - animateEmojis + animateEmojis, + showBotOverlay ) { override fun createAccountViewHolder(parent: ViewGroup): BlockedUserViewHolder { val view = LayoutInflater.from(parent.context) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.kt index 672f1fcac..5c5463056 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.kt @@ -16,23 +16,37 @@ package com.keylesspalace.tusky.adapter import android.view.LayoutInflater import android.view.ViewGroup -import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.databinding.ItemAccountBinding import com.keylesspalace.tusky.interfaces.AccountActionListener /** Displays either a follows or following list. */ class FollowAdapter( accountActionListener: AccountActionListener, animateAvatar: Boolean, - animateEmojis: Boolean -) : AccountAdapter(accountActionListener, animateAvatar, animateEmojis) { + animateEmojis: Boolean, + showBotOverlay: Boolean +) : AccountAdapter( + accountActionListener, + animateAvatar, + animateEmojis, + showBotOverlay +) { override fun createAccountViewHolder(parent: ViewGroup): AccountViewHolder { - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.item_account, parent, false) - return AccountViewHolder(view) + val binding = ItemAccountBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return AccountViewHolder(binding) } override fun onBindAccountViewHolder(viewHolder: AccountViewHolder, position: Int) { - viewHolder.setupWithAccount(accountList[position], animateAvatar, animateEmojis) + viewHolder.setupWithAccount( + accountList[position], + animateAvatar, + animateEmojis, + showBotOverlay + ) viewHolder.setupActionListener(accountActionListener) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt index 38b301b3a..60770ddad 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt @@ -34,7 +34,12 @@ class FollowRequestViewHolder( private val showHeader: Boolean ) : 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 emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView, animateEmojis) binding.displayNameTextView.text = emojifiedName diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.kt index 9b0a5dd90..95d944bda 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.kt @@ -23,8 +23,9 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener class FollowRequestsAdapter( accountActionListener: AccountActionListener, animateAvatar: Boolean, - animateEmojis: Boolean -) : AccountAdapter(accountActionListener, animateAvatar, animateEmojis) { + animateEmojis: Boolean, + showBotOverlay: Boolean +) : AccountAdapter(accountActionListener, animateAvatar, animateEmojis, showBotOverlay) { override fun createAccountViewHolder(parent: ViewGroup): FollowRequestViewHolder { val binding = ItemFollowRequestBinding.inflate( LayoutInflater.from(parent.context), parent, false @@ -33,7 +34,7 @@ class FollowRequestsAdapter( } 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) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.kt index 41bb286f5..979141d4a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.kt @@ -13,7 +13,6 @@ import com.keylesspalace.tusky.entity.TimelineAccount import com.keylesspalace.tusky.interfaces.AccountActionListener import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.loadAvatar -import java.util.HashMap /** * Displays a list of muted accounts with mute/unmute account and mute/unmute notifications @@ -22,11 +21,13 @@ import java.util.HashMap class MutesAdapter( accountActionListener: AccountActionListener, animateAvatar: Boolean, - animateEmojis: Boolean + animateEmojis: Boolean, + showBotOverlay: Boolean ) : AccountAdapter( accountActionListener, animateAvatar, - animateEmojis + animateEmojis, + showBotOverlay ) { private val mutingNotificationsMap = HashMap() diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java index 4efa38150..e7c6eeee9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -253,7 +253,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { case VIEW_TYPE_FOLLOW_REQUEST: { if (payloadForHolder == null) { 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()); } break; diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt index c4a3e8261..a8b913081 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt @@ -19,24 +19,27 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil -import com.keylesspalace.tusky.R import com.keylesspalace.tusky.adapter.AccountViewHolder +import com.keylesspalace.tusky.databinding.ItemAccountBinding import com.keylesspalace.tusky.entity.TimelineAccount 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(ACCOUNT_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder { - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.item_account, parent, false) - return AccountViewHolder(view) + val binding = ItemAccountBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return AccountViewHolder(binding) } override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { getItem(position)?.let { item -> holder.apply { - setupWithAccount(item, animateAvatars, animateEmojis) + setupWithAccount(item, animateAvatars, animateEmojis, showBotOverlay) setupLinkListener(linkListener) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt index f59f84ff6..87be69807 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt @@ -30,7 +30,8 @@ class SearchAccountsFragment : SearchFragment() { return SearchAccountsAdapter( this, 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) ) } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt index 19128eb7f..96301c08b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt @@ -95,17 +95,18 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct val pm = PreferenceManager.getDefaultSharedPreferences(view.context) val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false) val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + val showBotOverlay = pm.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true) adapter = when (type) { - Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis) - Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis) + Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis, showBotOverlay) + Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis, showBotOverlay) Type.FOLLOW_REQUESTS -> { 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) followRequestsAdapter } - else -> FollowAdapter(this, animateAvatar, animateEmojis) + else -> FollowAdapter(this, animateAvatar, animateEmojis, showBotOverlay) } if (binding.recyclerView.adapter == null) { binding.recyclerView.adapter = adapter diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml index c1565e093..805618493 100644 --- a/app/src/main/res/layout/item_account.xml +++ b/app/src/main/res/layout/item_account.xml @@ -21,12 +21,13 @@ tools:src="@drawable/avatar_default" />