migrating to ViewBinding part 4: Adapters (#2095)

This commit is contained in:
Konrad Pozniak 2021-03-07 19:24:01 +01:00 committed by GitHub
parent 22bed19d90
commit fc4b47aee4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 424 additions and 543 deletions

View File

@ -19,60 +19,57 @@ import android.text.method.LinkMovementMethod
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.View
import android.widget.TextView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAccountFieldBinding
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.Field
import com.keylesspalace.tusky.entity.IdentityProof
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.*
import kotlinx.android.synthetic.main.item_account_field.view.*
class AccountFieldAdapter(private val linkListener: LinkListener, private val animateEmojis: Boolean) : RecyclerView.Adapter<AccountFieldAdapter.ViewHolder>() {
class AccountFieldAdapter(
private val linkListener: LinkListener,
private val animateEmojis: Boolean
) : RecyclerView.Adapter<BindingHolder<ItemAccountFieldBinding>>() {
var emojis: List<Emoji> = emptyList()
var fields: List<Either<IdentityProof, Field>> = emptyList()
override fun getItemCount() = fields.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_account_field, parent, false)
return ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAccountFieldBinding> {
val binding = ItemAccountFieldBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
override fun onBindViewHolder(holder: BindingHolder<ItemAccountFieldBinding>, position: Int) {
val proofOrField = fields[position]
val nameTextView = holder.binding.accountFieldName
val valueTextView = holder.binding.accountFieldValue
if(proofOrField.isLeft()) {
val identityProof = proofOrField.asLeft()
viewHolder.nameTextView.text = identityProof.provider
viewHolder.valueTextView.text = LinkHelper.createClickableText(identityProof.username, identityProof.profileUrl)
nameTextView.text = identityProof.provider
valueTextView.text = LinkHelper.createClickableText(identityProof.username, identityProof.profileUrl)
viewHolder.valueTextView.movementMethod = LinkMovementMethod.getInstance()
valueTextView.movementMethod = LinkMovementMethod.getInstance()
viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
} else {
val field = proofOrField.asRight()
val emojifiedName = field.name.emojify(emojis, viewHolder.nameTextView, animateEmojis)
viewHolder.nameTextView.text = emojifiedName
val emojifiedName = field.name.emojify(emojis, nameTextView, animateEmojis)
nameTextView.text = emojifiedName
val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView, animateEmojis)
LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener)
val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis)
LinkHelper.setClickableText(valueTextView, emojifiedValue, null, linkListener)
if(field.verifiedAt != null) {
viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
} else {
viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0 )
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0 )
}
}
}
class ViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) {
val nameTextView: TextView = rootView.accountFieldName
val valueTextView: TextView = rootView.accountFieldValue
}
}

View File

@ -15,18 +15,16 @@
package com.keylesspalace.tusky.adapter
import androidx.recyclerview.widget.RecyclerView
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import com.keylesspalace.tusky.R
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.databinding.ItemEditFieldBinding
import com.keylesspalace.tusky.entity.StringField
import kotlinx.android.synthetic.main.item_edit_field.view.*
import com.keylesspalace.tusky.util.BindingHolder
class AccountFieldEditAdapter : RecyclerView.Adapter<AccountFieldEditAdapter.ViewHolder>() {
class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditFieldBinding>>() {
private val fieldData = mutableListOf<MutableStringPair>()
@ -54,20 +52,20 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<AccountFieldEditAdapter.Vie
notifyItemInserted(fieldData.size - 1)
}
override fun getItemCount(): Int = fieldData.size
override fun getItemCount() = fieldData.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_edit_field, parent, false)
return ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemEditFieldBinding> {
val binding = ItemEditFieldBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.nameTextView.setText(fieldData[position].first)
viewHolder.valueTextView.setText(fieldData[position].second)
override fun onBindViewHolder(holder: BindingHolder<ItemEditFieldBinding>, position: Int) {
holder.binding.accountFieldName.setText(fieldData[position].first)
holder.binding.accountFieldValue.setText(fieldData[position].second)
viewHolder.nameTextView.addTextChangedListener(object: TextWatcher {
holder.binding.accountFieldName.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(newText: Editable) {
fieldData[viewHolder.adapterPosition].first = newText.toString()
fieldData[holder.adapterPosition].first = newText.toString()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
@ -75,9 +73,9 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<AccountFieldEditAdapter.Vie
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
})
viewHolder.valueTextView.addTextChangedListener(object: TextWatcher {
holder.binding.accountFieldValue.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(newText: Editable) {
fieldData[viewHolder.adapterPosition].second = newText.toString()
fieldData[holder.adapterPosition].second = newText.toString()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
@ -87,12 +85,6 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<AccountFieldEditAdapter.Vie
}
class ViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) {
val nameTextView: EditText = rootView.accountFieldName
val valueTextView: EditText = rootView.accountFieldValue
}
class MutableStringPair (var first: String, var second: String)
}

View File

@ -22,39 +22,35 @@ import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAutocompleteAccountBinding
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.*
import kotlinx.android.synthetic.main.item_autocomplete_account.view.*
class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(context, R.layout.item_autocomplete_account) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var view = convertView
if (convertView == null) {
val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
view = layoutInflater.inflate(R.layout.item_autocomplete_account, parent, false)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding = if (convertView == null) {
ItemAutocompleteAccountBinding.inflate(LayoutInflater.from(context), parent, false)
} else {
ItemAutocompleteAccountBinding.bind(convertView)
}
view!!
val account = getItem(position)
if (account != null) {
val username = view.username
val displayName = view.display_name
val avatar = view.avatar
val pm = PreferenceManager.getDefaultSharedPreferences(avatar.context)
val pm = PreferenceManager.getDefaultSharedPreferences(binding.avatar.context)
val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
username.text = account.fullName
displayName.text = account.displayName.emojify(account.emojis, displayName, animateEmojis)
binding.username.text = account.fullName
binding.displayName.text = account.displayName.emojify(account.emojis, binding.displayName, animateEmojis)
val avatarRadius = avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp)
val avatarRadius = context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp)
val animateAvatar = pm.getBoolean("animateGifAvatars", false)
loadAvatar(account.profilePictureUrl, avatar, avatarRadius, animateAvatar)
loadAvatar(account.profilePictureUrl, binding.avatar, avatarRadius, animateAvatar)
}
return view
return binding.root
}
}

View File

@ -15,48 +15,44 @@
package com.keylesspalace.tusky.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemEmojiButtonBinding
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.util.BindingHolder
import java.util.*
class EmojiAdapter(emojiList: List<Emoji>, private val onEmojiSelectedListener: OnEmojiSelectedListener) : RecyclerView.Adapter<EmojiAdapter.EmojiHolder>() {
private val emojiList : List<Emoji>
class EmojiAdapter(
emojiList: List<Emoji>,
private val onEmojiSelectedListener: OnEmojiSelectedListener
) : RecyclerView.Adapter<BindingHolder<ItemEmojiButtonBinding>>() {
init {
this.emojiList = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
.sortedBy { it.shortcode.toLowerCase(Locale.ROOT) }
private val emojiList : List<Emoji> = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
.sortedBy { it.shortcode.toLowerCase(Locale.ROOT) }
override fun getItemCount() = emojiList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemEmojiButtonBinding> {
val binding = ItemEmojiButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
}
override fun getItemCount(): Int {
return emojiList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_emoji_button, parent, false) as ImageView
return EmojiHolder(view)
}
override fun onBindViewHolder(viewHolder: EmojiHolder, position: Int) {
override fun onBindViewHolder(holder: BindingHolder<ItemEmojiButtonBinding>, position: Int) {
val emoji = emojiList[position]
val emojiImageView = holder.binding.root
Glide.with(viewHolder.emojiImageView)
Glide.with(emojiImageView)
.load(emoji.url)
.into(viewHolder.emojiImageView)
.into(emojiImageView)
viewHolder.emojiImageView.setOnClickListener {
emojiImageView.setOnClickListener {
onEmojiSelectedListener.onEmojiSelected(emoji.shortcode)
}
viewHolder.emojiImageView.contentDescription = emoji.shortcode
emojiImageView.contentDescription = emoji.shortcode
}
class EmojiHolder(val emojiImageView: ImageView) : RecyclerView.ViewHolder(emojiImageView)
}
interface OnEmojiSelectedListener {

View File

@ -1,55 +1,67 @@
/* Copyright 2021 Tusky Contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.adapter
import android.graphics.Typeface
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.StyleSpan
import android.view.View
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.interfaces.AccountActionListener
import com.keylesspalace.tusky.util.*
import kotlinx.android.synthetic.main.item_follow_request_notification.view.*
internal class FollowRequestViewHolder(
itemView: View,
private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) {
private var id: String? = null
class FollowRequestViewHolder(
private val binding: ItemFollowRequestBinding,
private val showHeader: Boolean
) : RecyclerView.ViewHolder(binding.root) {
fun setupWithAccount(account: Account, animateAvatar: Boolean, animateEmojis: Boolean) {
id = account.id
val wrappedName = account.name.unicodeWrap()
val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView, animateEmojis)
itemView.displayNameTextView.text = emojifiedName
binding.displayNameTextView.text = emojifiedName
if (showHeader) {
val wholeMessage: String = itemView.context.getString(R.string.notification_follow_request_format, wrappedName)
itemView.notificationTextView?.text = SpannableStringBuilder(wholeMessage).apply {
binding.notificationTextView.text = SpannableStringBuilder(wholeMessage).apply {
setSpan(StyleSpan(Typeface.BOLD), 0, wrappedName.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}.emojify(account.emojis, itemView, animateEmojis)
}
itemView.notificationTextView?.visible(showHeader)
binding.notificationTextView.visible(showHeader)
val format = itemView.context.getString(R.string.status_username_format)
val formattedUsername = String.format(format, account.username)
itemView.usernameTextView.text = formattedUsername
val avatarRadius = itemView.avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
loadAvatar(account.avatar, itemView.avatar, avatarRadius, animateAvatar)
binding.usernameTextView.text = formattedUsername
val avatarRadius = binding.avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
loadAvatar(account.avatar, binding.avatar, avatarRadius, animateAvatar)
}
fun setupActionListener(listener: AccountActionListener) {
itemView.acceptButton.setOnClickListener {
fun setupActionListener(listener: AccountActionListener, accountId: String) {
binding.acceptButton.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.onRespondToFollowRequest(true, id, position)
listener.onRespondToFollowRequest(true, accountId, position)
}
}
itemView.rejectButton.setOnClickListener {
binding.rejectButton.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.onRespondToFollowRequest(false, id, position)
listener.onRespondToFollowRequest(false, accountId, position)
}
}
itemView.setOnClickListener { listener.onViewAccount(id) }
itemView.setOnClickListener { listener.onViewAccount(accountId) }
}
}

View File

@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
public class FollowRequestsAdapter extends AccountAdapter {
@ -37,9 +38,8 @@ public class FollowRequestsAdapter extends AccountAdapter {
switch (viewType) {
default:
case VIEW_TYPE_ACCOUNT: {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_follow_request, parent, false);
return new FollowRequestViewHolder(view, false);
ItemFollowRequestBinding binding = ItemFollowRequestBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new FollowRequestViewHolder(binding, false);
}
case VIEW_TYPE_FOOTER: {
View view = LayoutInflater.from(parent.getContext())
@ -54,7 +54,7 @@ public class FollowRequestsAdapter extends AccountAdapter {
if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) {
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis);
holder.setupActionListener(accountActionListener);
holder.setupActionListener(accountActionListener, accountList.get(position).getId());
}
}
}

View File

@ -1,16 +0,0 @@
package com.keylesspalace.tusky.adapter
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.interfaces.LinkListener
class HashtagViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val hashtag: TextView = itemView.findViewById(R.id.hashtag)
fun setup(tag: String, listener: LinkListener) {
hashtag.text = String.format("#%s", tag)
hashtag.setOnClickListener { listener.onViewTag(tag) }
}
}

View File

@ -21,21 +21,22 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemPickerListBinding
import com.keylesspalace.tusky.entity.MastoList
import kotlinx.android.synthetic.main.item_picker_list.view.*
class ListSelectionAdapter(context: Context) : ArrayAdapter<MastoList>(context, R.layout.item_autocomplete_hashtag) {
class ListSelectionAdapter(context: Context) : ArrayAdapter<MastoList>(context, R.layout.item_picker_list) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = convertView
?: layoutInflater.inflate(R.layout.item_picker_list, parent, false)
getItem(position)?.let { list ->
view.title.text = list.title
val binding = if (convertView == null) {
ItemPickerListBinding.inflate(LayoutInflater.from(context), parent, false)
} else {
ItemPickerListBinding.bind(convertView)
}
return view
getItem(position)?.let { list ->
binding.root.text = list.title
}
return binding.root
}
}

View File

@ -9,7 +9,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;

View File

@ -16,29 +16,28 @@
package com.keylesspalace.tusky.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import com.keylesspalace.tusky.databinding.ItemNetworkStateBinding
import com.keylesspalace.tusky.util.NetworkState
import com.keylesspalace.tusky.util.Status
import com.keylesspalace.tusky.util.visible
import kotlinx.android.synthetic.main.item_network_state.view.*
class NetworkStateViewHolder(itemView: View,
class NetworkStateViewHolder(private val binding: ItemNetworkStateBinding,
private val retryCallback: () -> Unit)
: RecyclerView.ViewHolder(itemView) {
: RecyclerView.ViewHolder(binding.root) {
fun setUpWithNetworkState(state: NetworkState?, fullScreen: Boolean) {
itemView.progressBar.visible(state?.status == Status.RUNNING)
itemView.retryButton.visible(state?.status == Status.FAILED)
itemView.errorMsg.visible(state?.msg != null)
itemView.errorMsg.text = state?.msg
itemView.retryButton.setOnClickListener {
binding.progressBar.visible(state?.status == Status.RUNNING)
binding.retryButton.visible(state?.status == Status.FAILED)
binding.errorMsg.visible(state?.msg != null)
binding.errorMsg.text = state?.msg
binding.retryButton.setOnClickListener {
retryCallback()
}
if(fullScreen) {
itemView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.root.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
} else {
itemView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
binding.root.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
}

View File

@ -39,6 +39,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.entity.Notification;
@ -125,9 +126,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
return new FollowViewHolder(view, statusDisplayOptions);
}
case VIEW_TYPE_FOLLOW_REQUEST: {
View view = inflater
.inflate(R.layout.item_follow_request_notification, parent, false);
return new FollowRequestViewHolder(view, true);
ItemFollowRequestBinding binding = ItemFollowRequestBinding.inflate(inflater, parent, false);
return new FollowRequestViewHolder(binding, true);
}
case VIEW_TYPE_PLACEHOLDER: {
View view = inflater
@ -233,7 +233,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
if (payloadForHolder == null) {
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
holder.setupWithAccount(concreteNotificaton.getAccount(), statusDisplayOptions.animateAvatars(), statusDisplayOptions.animateEmojis());
holder.setupActionListener(accountActionListener);
holder.setupActionListener(accountActionListener, concreteNotificaton.getAccount().getId());
}
}
default:

View File

@ -18,19 +18,18 @@ package com.keylesspalace.tusky.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.RadioButton
import android.widget.TextView
import androidx.emoji.text.EmojiCompat
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemPollBinding
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.viewdata.PollOptionViewData
import com.keylesspalace.tusky.viewdata.buildDescription
import com.keylesspalace.tusky.viewdata.calculatePercent
class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
private var pollOptions: List<PollOptionViewData> = emptyList()
private var voteCount: Int = 0
@ -64,39 +63,42 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollViewHolder {
return PollViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_poll, parent, false))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemPollBinding> {
val binding = ItemPollBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
}
override fun getItemCount(): Int {
return pollOptions.size
}
override fun getItemCount() = pollOptions.size
override fun onBindViewHolder(holder: PollViewHolder, position: Int) {
override fun onBindViewHolder(holder: BindingHolder<ItemPollBinding>, position: Int) {
val option = pollOptions[position]
holder.resultTextView.visible(mode == RESULT)
holder.radioButton.visible(mode == SINGLE)
holder.checkBox.visible(mode == MULTIPLE)
val resultTextView = holder.binding.statusPollOptionResult
val radioButton = holder.binding.statusPollRadioButton
val checkBox = holder.binding.statusPollCheckbox
resultTextView.visible(mode == RESULT)
radioButton.visible(mode == SINGLE)
checkBox.visible(mode == MULTIPLE)
when(mode) {
RESULT -> {
val percent = calculatePercent(option.votesCount, votersCount, voteCount)
val emojifiedPollOptionText = buildDescription(option.title, percent, holder.resultTextView.context)
.emojify(emojis, holder.resultTextView, animateEmojis)
holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
val emojifiedPollOptionText = buildDescription(option.title, percent, resultTextView.context)
.emojify(emojis, resultTextView, animateEmojis)
resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
val level = percent * 100
holder.resultTextView.background.level = level
holder.resultTextView.setOnClickListener(resultClickListener)
resultTextView.background.level = level
resultTextView.setOnClickListener(resultClickListener)
}
SINGLE -> {
val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton, animateEmojis)
holder.radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText)
holder.radioButton.isChecked = option.selected
holder.radioButton.setOnClickListener {
val emojifiedPollOptionText = option.title.emojify(emojis, radioButton, animateEmojis)
radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText)
radioButton.isChecked = option.selected
radioButton.setOnClickListener {
pollOptions.forEachIndexed { index, pollOption ->
pollOption.selected = index == holder.adapterPosition
notifyItemChanged(index)
@ -104,10 +106,10 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
}
}
MULTIPLE -> {
val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox, animateEmojis)
holder.checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText)
holder.checkBox.isChecked = option.selected
holder.checkBox.setOnCheckedChangeListener { _, isChecked ->
val emojifiedPollOptionText = option.title.emojify(emojis, checkBox, animateEmojis)
checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText)
checkBox.isChecked = option.selected
checkBox.setOnCheckedChangeListener { _, isChecked ->
pollOptions[holder.adapterPosition].selected = isChecked
}
}
@ -121,13 +123,3 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
const val MULTIPLE = 2
}
}
class PollViewHolder(view: View): RecyclerView.ViewHolder(view) {
val resultTextView: TextView = view.findViewById(R.id.status_poll_option_result)
val radioButton: RadioButton = view.findViewById(R.id.status_poll_radio_button)
val checkBox: CheckBox = view.findViewById(R.id.status_poll_checkbox)
}

View File

@ -63,5 +63,4 @@ class PreviewPollOptionsAdapter: RecyclerView.Adapter<PreviewViewHolder>() {
}
class PreviewViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
class PreviewViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)

View File

@ -18,19 +18,21 @@ package com.keylesspalace.tusky.adapter
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.core.view.size
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.google.android.material.chip.Chip
import com.keylesspalace.tusky.HASHTAG
import com.keylesspalace.tusky.LIST
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.TabData
import com.keylesspalace.tusky.databinding.ItemTabPreferenceBinding
import com.keylesspalace.tusky.databinding.ItemTabPreferenceSmallBinding
import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import kotlinx.android.synthetic.main.item_tab_preference.view.*
interface ItemInteractionListener {
fun onTabAdded(tab: TabData)
@ -44,61 +46,69 @@ interface ItemInteractionListener {
class TabAdapter(private var data: List<TabData>,
private val small: Boolean,
private val listener: ItemInteractionListener,
private var removeButtonEnabled: Boolean = false) : RecyclerView.Adapter<TabAdapter.ViewHolder>() {
private var removeButtonEnabled: Boolean = false
) : RecyclerView.Adapter<BindingHolder<ViewBinding>>() {
fun updateData(newData: List<TabData>) {
this.data = newData
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutId = if (small) {
R.layout.item_tab_preference_small
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ViewBinding> {
val binding = if (small) {
ItemTabPreferenceSmallBinding.inflate(LayoutInflater.from(parent.context), parent, false)
} else {
R.layout.item_tab_preference
ItemTabPreferenceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
}
val view = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
return ViewHolder(view)
return BindingHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
override fun onBindViewHolder(holder: BindingHolder<ViewBinding>, position: Int) {
val context = holder.itemView.context
val tab = data[position]
if (!small && tab.id == LIST) {
holder.itemView.textView.text = tab.arguments.getOrNull(1).orEmpty()
} else {
holder.itemView.textView.setText(tab.text)
}
holder.itemView.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(tab.icon, 0, 0, 0)
if (small) {
holder.itemView.textView.setOnClickListener {
val binding = holder.binding as ItemTabPreferenceSmallBinding
binding.textView.setText(tab.text)
binding.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(tab.icon, 0, 0, 0)
binding.textView.setOnClickListener {
listener.onTabAdded(tab)
}
}
holder.itemView.imageView?.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
listener.onStartDrag(holder)
true
} else {
val binding = holder.binding as ItemTabPreferenceBinding
if (tab.id == LIST) {
binding.textView.text = tab.arguments.getOrNull(1).orEmpty()
} else {
false
binding.textView.setText(tab.text)
}
}
holder.itemView.removeButton?.setOnClickListener {
listener.onTabRemoved(holder.adapterPosition)
}
if (holder.itemView.removeButton != null) {
holder.itemView.removeButton.isEnabled = removeButtonEnabled
binding.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(tab.icon, 0, 0, 0)
binding.imageView.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
listener.onStartDrag(holder)
true
} else {
false
}
}
binding.removeButton.setOnClickListener {
listener.onTabRemoved(holder.adapterPosition)
}
binding.removeButton.isEnabled = removeButtonEnabled
ThemeUtils.setDrawableTint(
holder.itemView.context,
holder.itemView.removeButton.drawable,
binding.removeButton.drawable,
(if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled)
)
}
if (!small) {
if (tab.id == HASHTAG) {
holder.itemView.chipGroup.show()
binding.chipGroup.show()
/*
* The chip group will always contain the actionChip (it is defined in the xml layout).
@ -107,9 +117,9 @@ class TabAdapter(private var data: List<TabData>,
*/
tab.arguments.forEachIndexed { i, arg ->
val chip = holder.itemView.chipGroup.getChildAt(i).takeUnless { it.id == R.id.actionChip } as Chip?
val chip = binding.chipGroup.getChildAt(i).takeUnless { it.id == R.id.actionChip } as Chip?
?: Chip(context).apply {
holder.itemView.chipGroup.addView(this, holder.itemView.chipGroup.size - 1)
binding.chipGroup.addView(this, binding.chipGroup.size - 1)
chipIconTint = ColorStateList.valueOf(ThemeUtils.getColor(context, android.R.attr.textColorPrimary))
}
@ -126,16 +136,16 @@ class TabAdapter(private var data: List<TabData>,
}
}
while(holder.itemView.chipGroup.size - 1 > tab.arguments.size) {
holder.itemView.chipGroup.removeViewAt(tab.arguments.size)
while(binding.chipGroup.size - 1 > tab.arguments.size) {
binding.chipGroup.removeViewAt(tab.arguments.size)
}
holder.itemView.actionChip.setOnClickListener {
binding.actionChip.setOnClickListener {
listener.onActionChipClicked(tab, holder.adapterPosition)
}
} else {
holder.itemView.chipGroup.hide()
binding.chipGroup.hide()
}
}
}
@ -148,6 +158,4 @@ class TabAdapter(private var data: List<TabData>,
notifyDataSetChanged()
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}

View File

@ -19,19 +19,17 @@ import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.size
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAnnouncementBinding
import com.keylesspalace.tusky.entity.Announcement
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.LinkHelper
import com.keylesspalace.tusky.util.emojify
import kotlinx.android.synthetic.main.item_announcement.view.*
interface AnnouncementActionListener: LinkListener {
fun openReactionPicker(announcementId: String, target: View)
@ -44,16 +42,74 @@ class AnnouncementAdapter(
private val listener: AnnouncementActionListener,
private val wellbeingEnabled: Boolean = false,
private val animateEmojis: Boolean = false
) : RecyclerView.Adapter<AnnouncementAdapter.AnnouncementViewHolder>() {
) : RecyclerView.Adapter<BindingHolder<ItemAnnouncementBinding>>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnnouncementViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_announcement, parent, false)
return AnnouncementViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAnnouncementBinding> {
val binding = ItemAnnouncementBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
}
override fun onBindViewHolder(viewHolder: AnnouncementViewHolder, position: Int) {
viewHolder.bind(items[position])
override fun onBindViewHolder(holder: BindingHolder<ItemAnnouncementBinding>, position: Int) {
val item = items[position]
val text = holder.binding.text
val chips = holder.binding.chipGroup
val addReactionChip = holder.binding.addReactionChip
LinkHelper.setClickableText(text, item.content, null, listener)
// If wellbeing mode is enabled, announcement badge counts should not be shown.
if (wellbeingEnabled) {
// Since reactions are not visible in wellbeing mode,
// we shouldn't be able to add any ourselves.
addReactionChip.visibility = View.GONE
return
}
item.reactions.forEachIndexed { i, reaction ->
(chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip?
?: Chip(ContextThemeWrapper(chips.context, R.style.Widget_MaterialComponents_Chip_Choice)).apply {
isCheckable = true
checkedIcon = null
chips.addView(this, i)
})
.apply {
val emojiText = if (reaction.url == null) {
reaction.name
} else {
context.getString(R.string.emoji_shortcode_format, reaction.name)
}
this.text = ("$emojiText ${reaction.count}")
.emojify(
listOf(Emoji(
reaction.name,
reaction.url ?: "",
reaction.staticUrl ?: "",
null
)),
this,
animateEmojis
)
isChecked = reaction.me
setOnClickListener {
if (reaction.me) {
listener.removeReaction(item.id, reaction.name)
} else {
listener.addReaction(item.id, reaction.name)
}
}
}
}
while (chips.size - 1 > item.reactions.size) {
chips.removeViewAt(item.reactions.size)
}
addReactionChip.setOnClickListener {
listener.openReactionPicker(item.id, it)
}
}
override fun getItemCount() = items.size
@ -62,67 +118,4 @@ class AnnouncementAdapter(
this.items = items
notifyDataSetChanged()
}
inner class AnnouncementViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
private val text: TextView = view.text
private val chips: ChipGroup = view.chipGroup
private val addReactionChip: Chip = view.addReactionChip
fun bind(item: Announcement) {
LinkHelper.setClickableText(text, item.content, null, listener)
// If wellbeing mode is enabled, announcement badge counts should not be shown.
if (wellbeingEnabled) {
// Since reactions are not visible in wellbeing mode,
// we shouldn't be able to add any ourselves.
addReactionChip.visibility = View.GONE
return
}
item.reactions.forEachIndexed { i, reaction ->
(chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip?
?: Chip(ContextThemeWrapper(view.context, R.style.Widget_MaterialComponents_Chip_Choice)).apply {
isCheckable = true
checkedIcon = null
chips.addView(this, i)
})
.apply {
val emojiText = if (reaction.url == null) {
reaction.name
} else {
view.context.getString(R.string.emoji_shortcode_format, reaction.name)
}
text = ("$emojiText ${reaction.count}")
.emojify(
listOf(Emoji(
reaction.name,
reaction.url ?: "",
reaction.staticUrl ?: "",
null
)),
this,
animateEmojis
)
isChecked = reaction.me
setOnClickListener {
if (reaction.me) {
listener.removeReaction(item.id, reaction.name)
} else {
listener.addReaction(item.id, reaction.name)
}
}
}
}
while (chips.size - 1 > item.reactions.size) {
chips.removeViewAt(item.reactions.size)
}
addReactionChip.setOnClickListener {
listener.openReactionPicker(item.id, it)
}
}
}
}

View File

@ -22,7 +22,6 @@ import android.view.LayoutInflater
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.AddPollOptionsAdapter
import com.keylesspalace.tusky.databinding.DialogAddPollBinding
import com.keylesspalace.tusky.entity.NewPoll

View File

@ -13,17 +13,16 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.adapter
package com.keylesspalace.tusky.components.compose.dialog
import android.text.InputFilter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAddPollOptionBinding
import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.onTextChanged
import com.keylesspalace.tusky.util.visible
@ -32,7 +31,7 @@ class AddPollOptionsAdapter(
private val maxOptionLength: Int,
private val onOptionRemoved: (Boolean) -> Unit,
private val onOptionChanged: (Boolean) -> Unit
): RecyclerView.Adapter<ViewHolder>() {
): RecyclerView.Adapter<BindingHolder<ItemAddPollOptionBinding>>() {
val pollOptions: List<String>
get() = options.toList()
@ -42,11 +41,12 @@ class AddPollOptionsAdapter(
notifyItemInserted(options.size - 1)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val holder = ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_add_poll_option, parent, false))
holder.editText.filters = arrayOf(InputFilter.LengthFilter(maxOptionLength))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAddPollOptionBinding> {
val binding = ItemAddPollOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
val holder = BindingHolder(binding)
binding.optionEditText.filters = arrayOf(InputFilter.LengthFilter(maxOptionLength))
holder.editText.onTextChanged { s, _, _, _ ->
binding.optionEditText.onTextChanged { s, _, _, _ ->
val pos = holder.adapterPosition
if(pos != RecyclerView.NO_POSITION) {
options[pos] = s.toString()
@ -59,15 +59,15 @@ class AddPollOptionsAdapter(
override fun getItemCount() = options.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.editText.setText(options[position])
override fun onBindViewHolder(holder: BindingHolder<ItemAddPollOptionBinding>, position: Int) {
holder.binding.optionEditText.setText(options[position])
holder.textInputLayout.hint = holder.textInputLayout.context.getString(R.string.poll_new_choice_hint, position + 1)
holder.binding.optionTextInputLayout.hint = holder.binding.root.context.getString(R.string.poll_new_choice_hint, position + 1)
holder.deleteButton.visible(position > 1, View.INVISIBLE)
holder.binding.deleteButton.visible(position > 1, View.INVISIBLE)
holder.deleteButton.setOnClickListener {
holder.editText.clearFocus()
holder.binding.deleteButton.setOnClickListener {
holder.binding.optionEditText.clearFocus()
options.removeAt(holder.adapterPosition)
notifyItemRemoved(holder.adapterPosition)
onOptionRemoved(validateInput())
@ -81,12 +81,4 @@ class AddPollOptionsAdapter(
return true
}
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val textInputLayout: TextInputLayout = itemView.findViewById(R.id.optionTextInputLayout)
val editText: TextInputEditText = itemView.findViewById(R.id.optionEditText)
val deleteButton: ImageButton = itemView.findViewById(R.id.deleteButton)
}

View File

@ -1,3 +1,18 @@
/* Copyright 2021 Tusky Contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.components.conversation
import android.view.LayoutInflater
@ -10,6 +25,7 @@ import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.NetworkStateViewHolder
import com.keylesspalace.tusky.databinding.ItemNetworkStateBinding
import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.util.NetworkState
import com.keylesspalace.tusky.util.StatusDisplayOptions
@ -49,11 +65,15 @@ class ConversationAdapter(
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) {
R.layout.item_network_state -> NetworkStateViewHolder(view, retryCallback)
R.layout.item_conversation -> ConversationViewHolder(view, statusDisplayOptions,
listener)
R.layout.item_network_state -> {
val binding = ItemNetworkStateBinding.inflate(LayoutInflater.from(parent.context), parent, false)
NetworkStateViewHolder(binding, retryCallback)
}
R.layout.item_conversation -> {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
ConversationViewHolder(view, statusDisplayOptions, listener)
}
else -> throw IllegalArgumentException("unknown view type $viewType")
}
}

View File

@ -23,7 +23,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.databinding.ItemDraftBinding
import com.keylesspalace.tusky.db.DraftEntity
import com.keylesspalace.tusky.util.BindingViewHolder
import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.visible
@ -35,7 +35,7 @@ interface DraftActionListener {
class DraftsAdapter(
private val listener: DraftActionListener
) : PagedListAdapter<DraftEntity, BindingViewHolder<ItemDraftBinding>>(
) : PagedListAdapter<DraftEntity, BindingHolder<ItemDraftBinding>>(
object : DiffUtil.ItemCallback<DraftEntity>() {
override fun areItemsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean {
return oldItem.id == newItem.id
@ -47,11 +47,11 @@ class DraftsAdapter(
}
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder<ItemDraftBinding> {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemDraftBinding> {
val binding = ItemDraftBinding.inflate(LayoutInflater.from(parent.context), parent, false)
val viewHolder = BindingViewHolder(binding)
val viewHolder = BindingHolder(binding)
binding.draftMediaPreview.layoutManager = LinearLayoutManager(binding.root.context, RecyclerView.HORIZONTAL, false)
binding.draftMediaPreview.adapter = DraftMediaAdapter {
@ -63,7 +63,7 @@ class DraftsAdapter(
return viewHolder
}
override fun onBindViewHolder(holder: BindingViewHolder<ItemDraftBinding>, position: Int) {
override fun onBindViewHolder(holder: BindingHolder<ItemDraftBinding>, position: Int) {
getItem(position)?.let { draft ->
holder.binding.root.setOnClickListener {
listener.onOpenDraft(draft)

View File

@ -1,22 +1,31 @@
package com.keylesspalace.tusky.components.instancemute.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener
import kotlinx.android.synthetic.main.item_muted_domain.view.*
import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding
import com.keylesspalace.tusky.util.BindingHolder
class DomainMutesAdapter(
private val actionListener: InstanceActionListener
): RecyclerView.Adapter<BindingHolder<ItemMutedDomainBinding>>() {
class DomainMutesAdapter(private val actionListener: InstanceActionListener): RecyclerView.Adapter<DomainMutesAdapter.ViewHolder>() {
var instances: MutableList<String> = mutableListOf()
var bottomLoading: Boolean = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_muted_domain, parent, false), actionListener)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemMutedDomainBinding> {
val binding = ItemMutedDomainBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setupWithInstance(instances[position])
override fun onBindViewHolder(holder: BindingHolder<ItemMutedDomainBinding>, position: Int) {
val instance = instances[position]
holder.binding.mutedDomain.text = instance
holder.binding.mutedDomainUnmute.setOnClickListener {
actionListener.mute(false, instance, holder.adapterPosition)
}
}
override fun getItemCount(): Int {
@ -37,21 +46,10 @@ class DomainMutesAdapter(private val actionListener: InstanceActionListener): Re
notifyItemInserted(instances.size)
}
fun removeItem(position: Int)
{
fun removeItem(position: Int) {
if (position >= 0 && position < instances.size) {
instances.removeAt(position)
notifyItemRemoved(position)
}
}
class ViewHolder(rootView: View, private val actionListener: InstanceActionListener): RecyclerView.ViewHolder(rootView) {
fun setupWithInstance(instance: String) {
itemView.muted_domain.text = instance
itemView.muted_domain_unmute.setOnClickListener {
actionListener.mute(false, instance, adapterPosition)
}
}
}
}
}

View File

@ -18,13 +18,11 @@ package com.keylesspalace.tusky.components.scheduled
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemScheduledTootBinding
import com.keylesspalace.tusky.entity.ScheduledStatus
import com.keylesspalace.tusky.util.BindingHolder
interface ScheduledTootActionListener {
fun edit(item: ScheduledStatus)
@ -33,7 +31,7 @@ interface ScheduledTootActionListener {
class ScheduledTootAdapter(
val listener: ScheduledTootActionListener
) : PagedListAdapter<ScheduledStatus, ScheduledTootAdapter.TootViewHolder>(
) : PagedListAdapter<ScheduledStatus, BindingHolder<ItemScheduledTootBinding>>(
object: DiffUtil.ItemCallback<ScheduledStatus>(){
override fun areItemsTheSame(oldItem: ScheduledStatus, newItem: ScheduledStatus): Boolean {
return oldItem.id == newItem.id
@ -46,40 +44,24 @@ class ScheduledTootAdapter(
}
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TootViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_scheduled_toot, parent, false)
return TootViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemScheduledTootBinding> {
val binding = ItemScheduledTootBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
}
override fun onBindViewHolder(viewHolder: TootViewHolder, position: Int) {
getItem(position)?.let{
viewHolder.bind(it)
}
}
inner class TootViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val text: TextView = view.findViewById(R.id.text)
private val edit: ImageButton = view.findViewById(R.id.edit)
private val delete: ImageButton = view.findViewById(R.id.delete)
fun bind(item: ScheduledStatus) {
edit.isEnabled = true
delete.isEnabled = true
text.text = item.params.text
edit.setOnClickListener { v: View ->
override fun onBindViewHolder(holder: BindingHolder<ItemScheduledTootBinding>, position: Int) {
getItem(position)?.let{ item ->
holder.binding.edit.isEnabled = true
holder.binding.delete.isEnabled = true
holder.binding.text.text = item.params.text
holder.binding.edit.setOnClickListener { v: View ->
v.isEnabled = false
listener.edit(item)
}
delete.setOnClickListener { v: View ->
holder.binding.delete.setOnClickListener { v: View ->
v.isEnabled = false
listener.delete(item)
}
}
}
}
}

View File

@ -19,24 +19,23 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.HashtagViewHolder
import com.keylesspalace.tusky.databinding.ItemHashtagBinding
import com.keylesspalace.tusky.entity.HashTag
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.BindingHolder
class SearchHashtagsAdapter(private val linkListener: LinkListener)
: PagedListAdapter<HashTag, RecyclerView.ViewHolder>(HASHTAG_COMPARATOR) {
: PagedListAdapter<HashTag, BindingHolder<ItemHashtagBinding>>(HASHTAG_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_hashtag, parent, false)
return HashtagViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemHashtagBinding> {
val binding = ItemHashtagBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
override fun onBindViewHolder(holder: BindingHolder<ItemHashtagBinding>, position: Int) {
getItem(position)?.let { (name) ->
(holder as HashtagViewHolder).setup(name, linkListener)
holder.binding.root.text = String.format("#%s", name)
holder.binding.root.setOnClickListener { linkListener.onViewTag(name) }
}
}

View File

@ -3,6 +3,6 @@ package com.keylesspalace.tusky.util
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
class BindingViewHolder<T : ViewBinding>(
class BindingHolder<T : ViewBinding>(
val binding: T
) : RecyclerView.ViewHolder(binding.root)

View File

@ -1,52 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="72dp"
android:gravity="center_vertical"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp">
android:paddingRight="16dp"
android:paddingBottom="10dp">
<androidx.emoji.widget.EmojiTextView
android:id="@+id/notificationTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:drawableStart="@drawable/ic_person_add_24dp"
android:drawablePadding="10dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingStart="28dp"
android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Someone requested to follow you" />
<ImageView
android:id="@+id/avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginEnd="24dp"
android:contentDescription="@string/action_view_profile" />
android:layout_marginTop="10dp"
android:contentDescription="@string/action_view_profile"
tools:src="@drawable/avatar_default"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/notificationTextView" />
<LinearLayout
<androidx.emoji.widget.EmojiTextView
android:id="@+id/displayNameTextView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toEndOf="@id/avatar"
android:layout_toStartOf="@id/acceptButton"
android:gravity="center_vertical"
android:orientation="vertical">
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="?attr/status_text_large"
android:textStyle="normal|bold"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/avatar"
app:layout_constraintBottom_toTopOf="@id/usernameTextView"
tools:text="Display name" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/displayNameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="?attr/status_text_large"
android:textStyle="normal|bold"
tools:text="Display name" />
<TextView
android:id="@+id/usernameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_medium"
tools:text="\@username" />
</LinearLayout>
<TextView
android:id="@+id/usernameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toBottomOf="@id/displayNameTextView"
app:layout_constraintBottom_toBottomOf="@id/avatar"
tools:text="\@username" />
<ImageButton
android:id="@+id/acceptButton"
@ -55,10 +72,12 @@
android:layout_height="32dp"
android:layout_centerVertical="true"
android:layout_marginStart="12dp"
android:layout_toStartOf="@id/rejectButton"
android:layout_marginTop="14dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_accept"
android:padding="4dp"
app:layout_constraintEnd_toStartOf="@id/rejectButton"
app:layout_constraintTop_toBottomOf="@id/notificationTextView"
app:srcCompat="@drawable/ic_check_24dp" />
<ImageButton
@ -66,12 +85,14 @@
style="@style/TuskyImageButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="12dp"
android:layout_marginTop="14dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_reject"
android:padding="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/notificationTextView"
app:srcCompat="@drawable/ic_reject_24dp" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,96 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingBottom="10dp">
<androidx.emoji.widget.EmojiTextView
android:id="@+id/notificationTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:drawableStart="@drawable/ic_person_add_24dp"
android:drawablePadding="10dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingStart="28dp"
android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Someone requested to follow you" />
<ImageView
android:id="@+id/avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:layout_marginTop="10dp"
android:contentDescription="@string/action_view_profile"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/notificationTextView" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/displayNameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginTop="6dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="?attr/status_text_large"
android:textStyle="normal|bold"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toBottomOf="@id/notificationTextView"
tools:text="Display name" />
<TextView
android:id="@+id/usernameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toBottomOf="@id/displayNameTextView"
tools:text="\@username" />
<ImageButton
android:id="@+id/acceptButton"
style="@style/TuskyImageButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_centerVertical="true"
android:layout_marginStart="12dp"
android:layout_marginTop="14dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_accept"
android:padding="4dp"
app:layout_constraintEnd_toStartOf="@id/rejectButton"
app:layout_constraintTop_toBottomOf="@id/notificationTextView"
app:srcCompat="@drawable/ic_check_24dp" />
<ImageButton
android:id="@+id/rejectButton"
style="@style/TuskyImageButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_centerVertical="true"
android:layout_marginStart="12dp"
android:layout_marginTop="14dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_reject"
android:padding="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/notificationTextView"
app:srcCompat="@drawable/ic_reject_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/hashtag"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"