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 androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.view.View
import android.widget.TextView
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAccountFieldBinding
import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.Field import com.keylesspalace.tusky.entity.Field
import com.keylesspalace.tusky.entity.IdentityProof import com.keylesspalace.tusky.entity.IdentityProof
import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.* 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 emojis: List<Emoji> = emptyList()
var fields: List<Either<IdentityProof, Field>> = emptyList() var fields: List<Either<IdentityProof, Field>> = emptyList()
override fun getItemCount() = fields.size override fun getItemCount() = fields.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAccountFieldBinding> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_account_field, parent, false) val binding = ItemAccountFieldBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(view) return BindingHolder(binding)
} }
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: BindingHolder<ItemAccountFieldBinding>, position: Int) {
val proofOrField = fields[position] val proofOrField = fields[position]
val nameTextView = holder.binding.accountFieldName
val valueTextView = holder.binding.accountFieldValue
if(proofOrField.isLeft()) { if(proofOrField.isLeft()) {
val identityProof = proofOrField.asLeft() val identityProof = proofOrField.asLeft()
viewHolder.nameTextView.text = identityProof.provider nameTextView.text = identityProof.provider
viewHolder.valueTextView.text = LinkHelper.createClickableText(identityProof.username, identityProof.profileUrl) 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 { } else {
val field = proofOrField.asRight() val field = proofOrField.asRight()
val emojifiedName = field.name.emojify(emojis, viewHolder.nameTextView, animateEmojis) val emojifiedName = field.name.emojify(emojis, nameTextView, animateEmojis)
viewHolder.nameTextView.text = emojifiedName nameTextView.text = emojifiedName
val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView, animateEmojis) val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis)
LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener) LinkHelper.setClickableText(valueTextView, emojifiedValue, null, linkListener)
if(field.verifiedAt != null) { 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 { } 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 package com.keylesspalace.tusky.adapter
import androidx.recyclerview.widget.RecyclerView
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.ItemEditFieldBinding
import com.keylesspalace.tusky.entity.StringField 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>() private val fieldData = mutableListOf<MutableStringPair>()
@ -54,20 +52,20 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<AccountFieldEditAdapter.Vie
notifyItemInserted(fieldData.size - 1) notifyItemInserted(fieldData.size - 1)
} }
override fun getItemCount(): Int = fieldData.size override fun getItemCount() = fieldData.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemEditFieldBinding> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_edit_field, parent, false) val binding = ItemEditFieldBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(view) return BindingHolder(binding)
} }
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: BindingHolder<ItemEditFieldBinding>, position: Int) {
viewHolder.nameTextView.setText(fieldData[position].first) holder.binding.accountFieldName.setText(fieldData[position].first)
viewHolder.valueTextView.setText(fieldData[position].second) holder.binding.accountFieldValue.setText(fieldData[position].second)
viewHolder.nameTextView.addTextChangedListener(object: TextWatcher { holder.binding.accountFieldName.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(newText: Editable) { 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) {} 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) {} 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) { 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) {} 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) class MutableStringPair (var first: String, var second: String)
} }

View file

@ -22,39 +22,35 @@ import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAutocompleteAccountBinding
import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.* 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) { 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) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater val binding = if (convertView == null) {
view = layoutInflater.inflate(R.layout.item_autocomplete_account, parent, false) ItemAutocompleteAccountBinding.inflate(LayoutInflater.from(context), parent, false)
} else {
ItemAutocompleteAccountBinding.bind(convertView)
} }
view!!
val account = getItem(position) val account = getItem(position)
if (account != null) { if (account != null) {
val username = view.username val pm = PreferenceManager.getDefaultSharedPreferences(binding.avatar.context)
val displayName = view.display_name
val avatar = view.avatar
val pm = PreferenceManager.getDefaultSharedPreferences(avatar.context)
val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
username.text = account.fullName binding.username.text = account.fullName
displayName.text = account.displayName.emojify(account.emojis, displayName, animateEmojis) 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) 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 package com.keylesspalace.tusky.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide 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.entity.Emoji
import com.keylesspalace.tusky.util.BindingHolder
import java.util.* import java.util.*
class EmojiAdapter(emojiList: List<Emoji>, private val onEmojiSelectedListener: OnEmojiSelectedListener) : RecyclerView.Adapter<EmojiAdapter.EmojiHolder>() { class EmojiAdapter(
private val emojiList : List<Emoji> emojiList: List<Emoji>,
private val onEmojiSelectedListener: OnEmojiSelectedListener
) : RecyclerView.Adapter<BindingHolder<ItemEmojiButtonBinding>>() {
init { private val emojiList : List<Emoji> = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
this.emojiList = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker } .sortedBy { it.shortcode.toLowerCase(Locale.ROOT) }
.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 { override fun onBindViewHolder(holder: BindingHolder<ItemEmojiButtonBinding>, position: 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) {
val emoji = emojiList[position] val emoji = emojiList[position]
val emojiImageView = holder.binding.root
Glide.with(viewHolder.emojiImageView) Glide.with(emojiImageView)
.load(emoji.url) .load(emoji.url)
.into(viewHolder.emojiImageView) .into(emojiImageView)
viewHolder.emojiImageView.setOnClickListener { emojiImageView.setOnClickListener {
onEmojiSelectedListener.onEmojiSelected(emoji.shortcode) onEmojiSelectedListener.onEmojiSelected(emoji.shortcode)
} }
viewHolder.emojiImageView.contentDescription = emoji.shortcode emojiImageView.contentDescription = emoji.shortcode
} }
class EmojiHolder(val emojiImageView: ImageView) : RecyclerView.ViewHolder(emojiImageView)
} }
interface OnEmojiSelectedListener { 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 package com.keylesspalace.tusky.adapter
import android.graphics.Typeface import android.graphics.Typeface
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.view.View
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.interfaces.AccountActionListener import com.keylesspalace.tusky.interfaces.AccountActionListener
import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.util.*
import kotlinx.android.synthetic.main.item_follow_request_notification.view.*
internal class FollowRequestViewHolder( class FollowRequestViewHolder(
itemView: View, private val binding: ItemFollowRequestBinding,
private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) { private val showHeader: Boolean
private var id: String? = null ) : RecyclerView.ViewHolder(binding.root) {
fun setupWithAccount(account: Account, animateAvatar: Boolean, animateEmojis: Boolean) { fun setupWithAccount(account: Account, animateAvatar: Boolean, animateEmojis: Boolean) {
id = account.id
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)
itemView.displayNameTextView.text = emojifiedName binding.displayNameTextView.text = emojifiedName
if (showHeader) { if (showHeader) {
val wholeMessage: String = itemView.context.getString(R.string.notification_follow_request_format, wrappedName) 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) setSpan(StyleSpan(Typeface.BOLD), 0, wrappedName.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}.emojify(account.emojis, itemView, animateEmojis) }.emojify(account.emojis, itemView, animateEmojis)
} }
itemView.notificationTextView?.visible(showHeader) binding.notificationTextView.visible(showHeader)
val format = itemView.context.getString(R.string.status_username_format) val format = itemView.context.getString(R.string.status_username_format)
val formattedUsername = String.format(format, account.username) val formattedUsername = String.format(format, account.username)
itemView.usernameTextView.text = formattedUsername binding.usernameTextView.text = formattedUsername
val avatarRadius = itemView.avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp) val avatarRadius = binding.avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
loadAvatar(account.avatar, itemView.avatar, avatarRadius, animateAvatar) loadAvatar(account.avatar, binding.avatar, avatarRadius, animateAvatar)
} }
fun setupActionListener(listener: AccountActionListener) { fun setupActionListener(listener: AccountActionListener, accountId: String) {
itemView.acceptButton.setOnClickListener { binding.acceptButton.setOnClickListener {
val position = adapterPosition val position = adapterPosition
if (position != RecyclerView.NO_POSITION) { if (position != RecyclerView.NO_POSITION) {
listener.onRespondToFollowRequest(true, id, position) listener.onRespondToFollowRequest(true, accountId, position)
} }
} }
itemView.rejectButton.setOnClickListener { binding.rejectButton.setOnClickListener {
val position = adapterPosition val position = adapterPosition
if (position != RecyclerView.NO_POSITION) { 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 androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding;
import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.AccountActionListener;
public class FollowRequestsAdapter extends AccountAdapter { public class FollowRequestsAdapter extends AccountAdapter {
@ -37,9 +38,8 @@ public class FollowRequestsAdapter extends AccountAdapter {
switch (viewType) { switch (viewType) {
default: default:
case VIEW_TYPE_ACCOUNT: { case VIEW_TYPE_ACCOUNT: {
View view = LayoutInflater.from(parent.getContext()) ItemFollowRequestBinding binding = ItemFollowRequestBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
.inflate(R.layout.item_follow_request, parent, false); return new FollowRequestViewHolder(binding, false);
return new FollowRequestViewHolder(view, false);
} }
case VIEW_TYPE_FOOTER: { case VIEW_TYPE_FOOTER: {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
@ -54,7 +54,7 @@ public class FollowRequestsAdapter extends AccountAdapter {
if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) {
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); 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.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemPickerListBinding
import com.keylesspalace.tusky.entity.MastoList 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 { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater val binding = if (convertView == null) {
ItemPickerListBinding.inflate(LayoutInflater.from(context), parent, false)
val view = convertView } else {
?: layoutInflater.inflate(R.layout.item_picker_list, parent, false) ItemPickerListBinding.bind(convertView)
getItem(position)?.let { list ->
view.title.text = list.title
} }
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.annotation.NonNull;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;

View file

@ -16,29 +16,28 @@
package com.keylesspalace.tusky.adapter package com.keylesspalace.tusky.adapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.keylesspalace.tusky.databinding.ItemNetworkStateBinding
import com.keylesspalace.tusky.util.NetworkState import com.keylesspalace.tusky.util.NetworkState
import com.keylesspalace.tusky.util.Status import com.keylesspalace.tusky.util.Status
import com.keylesspalace.tusky.util.visible 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) private val retryCallback: () -> Unit)
: RecyclerView.ViewHolder(itemView) { : RecyclerView.ViewHolder(binding.root) {
fun setUpWithNetworkState(state: NetworkState?, fullScreen: Boolean) { fun setUpWithNetworkState(state: NetworkState?, fullScreen: Boolean) {
itemView.progressBar.visible(state?.status == Status.RUNNING) binding.progressBar.visible(state?.status == Status.RUNNING)
itemView.retryButton.visible(state?.status == Status.FAILED) binding.retryButton.visible(state?.status == Status.FAILED)
itemView.errorMsg.visible(state?.msg != null) binding.errorMsg.visible(state?.msg != null)
itemView.errorMsg.text = state?.msg binding.errorMsg.text = state?.msg
itemView.retryButton.setOnClickListener { binding.retryButton.setOnClickListener {
retryCallback() retryCallback()
} }
if(fullScreen) { if(fullScreen) {
itemView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT binding.root.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
} else { } 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.bumptech.glide.Glide;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Emoji; import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.entity.Notification;
@ -125,9 +126,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
return new FollowViewHolder(view, statusDisplayOptions); return new FollowViewHolder(view, statusDisplayOptions);
} }
case VIEW_TYPE_FOLLOW_REQUEST: { case VIEW_TYPE_FOLLOW_REQUEST: {
View view = inflater ItemFollowRequestBinding binding = ItemFollowRequestBinding.inflate(inflater, parent, false);
.inflate(R.layout.item_follow_request_notification, parent, false); return new FollowRequestViewHolder(binding, true);
return new FollowRequestViewHolder(view, true);
} }
case VIEW_TYPE_PLACEHOLDER: { case VIEW_TYPE_PLACEHOLDER: {
View view = inflater View view = inflater
@ -233,7 +233,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
if (payloadForHolder == null) { if (payloadForHolder == null) {
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
holder.setupWithAccount(concreteNotificaton.getAccount(), statusDisplayOptions.animateAvatars(), statusDisplayOptions.animateEmojis()); holder.setupWithAccount(concreteNotificaton.getAccount(), statusDisplayOptions.animateAvatars(), statusDisplayOptions.animateEmojis());
holder.setupActionListener(accountActionListener); holder.setupActionListener(accountActionListener, concreteNotificaton.getAccount().getId());
} }
} }
default: default:

View file

@ -18,19 +18,18 @@ package com.keylesspalace.tusky.adapter
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.RadioButton
import android.widget.TextView
import androidx.emoji.text.EmojiCompat import androidx.emoji.text.EmojiCompat
import androidx.recyclerview.widget.RecyclerView 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.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.PollOptionViewData
import com.keylesspalace.tusky.viewdata.buildDescription import com.keylesspalace.tusky.viewdata.buildDescription
import com.keylesspalace.tusky.viewdata.calculatePercent 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 pollOptions: List<PollOptionViewData> = emptyList()
private var voteCount: Int = 0 private var voteCount: Int = 0
@ -64,39 +63,42 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemPollBinding> {
return PollViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_poll, parent, false)) val binding = ItemPollBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindingHolder(binding)
} }
override fun getItemCount(): Int { override fun getItemCount() = pollOptions.size
return pollOptions.size
}
override fun onBindViewHolder(holder: PollViewHolder, position: Int) { override fun onBindViewHolder(holder: BindingHolder<ItemPollBinding>, position: Int) {
val option = pollOptions[position] val option = pollOptions[position]
holder.resultTextView.visible(mode == RESULT) val resultTextView = holder.binding.statusPollOptionResult
holder.radioButton.visible(mode == SINGLE) val radioButton = holder.binding.statusPollRadioButton
holder.checkBox.visible(mode == MULTIPLE) val checkBox = holder.binding.statusPollCheckbox
resultTextView.visible(mode == RESULT)
radioButton.visible(mode == SINGLE)
checkBox.visible(mode == MULTIPLE)
when(mode) { when(mode) {
RESULT -> { RESULT -> {
val percent = calculatePercent(option.votesCount, votersCount, voteCount) val percent = calculatePercent(option.votesCount, votersCount, voteCount)
val emojifiedPollOptionText = buildDescription(option.title, percent, holder.resultTextView.context) val emojifiedPollOptionText = buildDescription(option.title, percent, resultTextView.context)
.emojify(emojis, holder.resultTextView, animateEmojis) .emojify(emojis, resultTextView, animateEmojis)
holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText) resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
val level = percent * 100 val level = percent * 100
holder.resultTextView.background.level = level resultTextView.background.level = level
holder.resultTextView.setOnClickListener(resultClickListener) resultTextView.setOnClickListener(resultClickListener)
} }
SINGLE -> { SINGLE -> {
val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton, animateEmojis) val emojifiedPollOptionText = option.title.emojify(emojis, radioButton, animateEmojis)
holder.radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText) radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText)
holder.radioButton.isChecked = option.selected radioButton.isChecked = option.selected
holder.radioButton.setOnClickListener { radioButton.setOnClickListener {
pollOptions.forEachIndexed { index, pollOption -> pollOptions.forEachIndexed { index, pollOption ->
pollOption.selected = index == holder.adapterPosition pollOption.selected = index == holder.adapterPosition
notifyItemChanged(index) notifyItemChanged(index)
@ -104,10 +106,10 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
} }
} }
MULTIPLE -> { MULTIPLE -> {
val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox, animateEmojis) val emojifiedPollOptionText = option.title.emojify(emojis, checkBox, animateEmojis)
holder.checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText) checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText)
holder.checkBox.isChecked = option.selected checkBox.isChecked = option.selected
holder.checkBox.setOnCheckedChangeListener { _, isChecked -> checkBox.setOnCheckedChangeListener { _, isChecked ->
pollOptions[holder.adapterPosition].selected = isChecked pollOptions[holder.adapterPosition].selected = isChecked
} }
} }
@ -121,13 +123,3 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
const val MULTIPLE = 2 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.content.res.ColorStateList
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.size import androidx.core.view.size
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.keylesspalace.tusky.HASHTAG import com.keylesspalace.tusky.HASHTAG
import com.keylesspalace.tusky.LIST import com.keylesspalace.tusky.LIST
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.TabData 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.ThemeUtils
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.show
import kotlinx.android.synthetic.main.item_tab_preference.view.*
interface ItemInteractionListener { interface ItemInteractionListener {
fun onTabAdded(tab: TabData) fun onTabAdded(tab: TabData)
@ -44,61 +46,69 @@ interface ItemInteractionListener {
class TabAdapter(private var data: List<TabData>, class TabAdapter(private var data: List<TabData>,
private val small: Boolean, private val small: Boolean,
private val listener: ItemInteractionListener, 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>) { fun updateData(newData: List<TabData>) {
this.data = newData this.data = newData
notifyDataSetChanged() notifyDataSetChanged()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ViewBinding> {
val layoutId = if (small) { val binding = if (small) {
R.layout.item_tab_preference_small ItemTabPreferenceSmallBinding.inflate(LayoutInflater.from(parent.context), parent, false)
} else { } 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 BindingHolder(binding)
return ViewHolder(view)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: BindingHolder<ViewBinding>, position: Int) {
val context = holder.itemView.context val context = holder.itemView.context
val tab = data[position] 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) { 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) listener.onTabAdded(tab)
} }
}
holder.itemView.imageView?.setOnTouchListener { _, event -> } else {
if (event.action == MotionEvent.ACTION_DOWN) { val binding = holder.binding as ItemTabPreferenceBinding
listener.onStartDrag(holder)
true if (tab.id == LIST) {
binding.textView.text = tab.arguments.getOrNull(1).orEmpty()
} else { } else {
false binding.textView.setText(tab.text)
} }
}
holder.itemView.removeButton?.setOnClickListener { binding.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(tab.icon, 0, 0, 0)
listener.onTabRemoved(holder.adapterPosition)
} binding.imageView.setOnTouchListener { _, event ->
if (holder.itemView.removeButton != null) { if (event.action == MotionEvent.ACTION_DOWN) {
holder.itemView.removeButton.isEnabled = removeButtonEnabled listener.onStartDrag(holder)
true
} else {
false
}
}
binding.removeButton.setOnClickListener {
listener.onTabRemoved(holder.adapterPosition)
}
binding.removeButton.isEnabled = removeButtonEnabled
ThemeUtils.setDrawableTint( ThemeUtils.setDrawableTint(
holder.itemView.context, holder.itemView.context,
holder.itemView.removeButton.drawable, binding.removeButton.drawable,
(if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled) (if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled)
) )
}
if (!small) {
if (tab.id == HASHTAG) { 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). * 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 -> 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 { ?: 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)) 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) { while(binding.chipGroup.size - 1 > tab.arguments.size) {
holder.itemView.chipGroup.removeViewAt(tab.arguments.size) binding.chipGroup.removeViewAt(tab.arguments.size)
} }
holder.itemView.actionChip.setOnClickListener { binding.actionChip.setOnClickListener {
listener.onActionChipClicked(tab, holder.adapterPosition) listener.onActionChipClicked(tab, holder.adapterPosition)
} }
} else { } else {
holder.itemView.chipGroup.hide() binding.chipGroup.hide()
} }
} }
} }
@ -148,6 +158,4 @@ class TabAdapter(private var data: List<TabData>,
notifyDataSetChanged() 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.size import androidx.core.view.size
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAnnouncementBinding
import com.keylesspalace.tusky.entity.Announcement import com.keylesspalace.tusky.entity.Announcement
import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.LinkHelper import com.keylesspalace.tusky.util.LinkHelper
import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.emojify
import kotlinx.android.synthetic.main.item_announcement.view.*
interface AnnouncementActionListener: LinkListener { interface AnnouncementActionListener: LinkListener {
fun openReactionPicker(announcementId: String, target: View) fun openReactionPicker(announcementId: String, target: View)
@ -44,16 +42,74 @@ class AnnouncementAdapter(
private val listener: AnnouncementActionListener, private val listener: AnnouncementActionListener,
private val wellbeingEnabled: Boolean = false, private val wellbeingEnabled: Boolean = false,
private val animateEmojis: Boolean = false private val animateEmojis: Boolean = false
) : RecyclerView.Adapter<AnnouncementAdapter.AnnouncementViewHolder>() { ) : RecyclerView.Adapter<BindingHolder<ItemAnnouncementBinding>>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnnouncementViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAnnouncementBinding> {
val view = LayoutInflater.from(parent.context) val binding = ItemAnnouncementBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.inflate(R.layout.item_announcement, parent, false) return BindingHolder(binding)
return AnnouncementViewHolder(view)
} }
override fun onBindViewHolder(viewHolder: AnnouncementViewHolder, position: Int) { override fun onBindViewHolder(holder: BindingHolder<ItemAnnouncementBinding>, position: Int) {
viewHolder.bind(items[position]) 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 override fun getItemCount() = items.size
@ -62,67 +118,4 @@ class AnnouncementAdapter(
this.items = items this.items = items
notifyDataSetChanged() 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 android.view.WindowManager
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.AddPollOptionsAdapter
import com.keylesspalace.tusky.databinding.DialogAddPollBinding import com.keylesspalace.tusky.databinding.DialogAddPollBinding
import com.keylesspalace.tusky.entity.NewPoll 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, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.adapter package com.keylesspalace.tusky.components.compose.dialog
import android.text.InputFilter import android.text.InputFilter
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageButton
import androidx.recyclerview.widget.RecyclerView 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.R
import com.keylesspalace.tusky.databinding.ItemAddPollOptionBinding
import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.onTextChanged import com.keylesspalace.tusky.util.onTextChanged
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
@ -32,7 +31,7 @@ class AddPollOptionsAdapter(
private val maxOptionLength: Int, private val maxOptionLength: Int,
private val onOptionRemoved: (Boolean) -> Unit, private val onOptionRemoved: (Boolean) -> Unit,
private val onOptionChanged: (Boolean) -> Unit private val onOptionChanged: (Boolean) -> Unit
): RecyclerView.Adapter<ViewHolder>() { ): RecyclerView.Adapter<BindingHolder<ItemAddPollOptionBinding>>() {
val pollOptions: List<String> val pollOptions: List<String>
get() = options.toList() get() = options.toList()
@ -42,11 +41,12 @@ class AddPollOptionsAdapter(
notifyItemInserted(options.size - 1) notifyItemInserted(options.size - 1)
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAddPollOptionBinding> {
val holder = ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_add_poll_option, parent, false)) val binding = ItemAddPollOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
holder.editText.filters = arrayOf(InputFilter.LengthFilter(maxOptionLength)) val holder = BindingHolder(binding)
binding.optionEditText.filters = arrayOf(InputFilter.LengthFilter(maxOptionLength))
holder.editText.onTextChanged { s, _, _, _ -> binding.optionEditText.onTextChanged { s, _, _, _ ->
val pos = holder.adapterPosition val pos = holder.adapterPosition
if(pos != RecyclerView.NO_POSITION) { if(pos != RecyclerView.NO_POSITION) {
options[pos] = s.toString() options[pos] = s.toString()
@ -59,15 +59,15 @@ class AddPollOptionsAdapter(
override fun getItemCount() = options.size override fun getItemCount() = options.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: BindingHolder<ItemAddPollOptionBinding>, position: Int) {
holder.editText.setText(options[position]) 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.binding.deleteButton.setOnClickListener {
holder.editText.clearFocus() holder.binding.optionEditText.clearFocus()
options.removeAt(holder.adapterPosition) options.removeAt(holder.adapterPosition)
notifyItemRemoved(holder.adapterPosition) notifyItemRemoved(holder.adapterPosition)
onOptionRemoved(validateInput()) onOptionRemoved(validateInput())
@ -81,12 +81,4 @@ class AddPollOptionsAdapter(
return true 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 package com.keylesspalace.tusky.components.conversation
import android.view.LayoutInflater import android.view.LayoutInflater
@ -10,6 +25,7 @@ import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.NetworkStateViewHolder import com.keylesspalace.tusky.adapter.NetworkStateViewHolder
import com.keylesspalace.tusky.databinding.ItemNetworkStateBinding
import com.keylesspalace.tusky.interfaces.StatusActionListener import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.util.NetworkState import com.keylesspalace.tusky.util.NetworkState
import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.StatusDisplayOptions
@ -49,11 +65,15 @@ class ConversationAdapter(
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) { return when (viewType) {
R.layout.item_network_state -> NetworkStateViewHolder(view, retryCallback) R.layout.item_network_state -> {
R.layout.item_conversation -> ConversationViewHolder(view, statusDisplayOptions, val binding = ItemNetworkStateBinding.inflate(LayoutInflater.from(parent.context), parent, false)
listener) 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") else -> throw IllegalArgumentException("unknown view type $viewType")
} }
} }

View file

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

View file

@ -1,22 +1,31 @@
package com.keylesspalace.tusky.components.instancemute.adapter package com.keylesspalace.tusky.components.instancemute.adapter
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener 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 instances: MutableList<String> = mutableListOf()
var bottomLoading: Boolean = false 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) { override fun onBindViewHolder(holder: BindingHolder<ItemMutedDomainBinding>, position: Int) {
holder.setupWithInstance(instances[position]) val instance = instances[position]
holder.binding.mutedDomain.text = instance
holder.binding.mutedDomainUnmute.setOnClickListener {
actionListener.mute(false, instance, holder.adapterPosition)
}
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
@ -37,21 +46,10 @@ class DomainMutesAdapter(private val actionListener: InstanceActionListener): Re
notifyItemInserted(instances.size) notifyItemInserted(instances.size)
} }
fun removeItem(position: Int) fun removeItem(position: Int) {
{
if (position >= 0 && position < instances.size) { if (position >= 0 && position < instances.size) {
instances.removeAt(position) instances.removeAt(position)
notifyItemRemoved(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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.paging.PagedListAdapter import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.databinding.ItemScheduledTootBinding
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.ScheduledStatus import com.keylesspalace.tusky.entity.ScheduledStatus
import com.keylesspalace.tusky.util.BindingHolder
interface ScheduledTootActionListener { interface ScheduledTootActionListener {
fun edit(item: ScheduledStatus) fun edit(item: ScheduledStatus)
@ -33,7 +31,7 @@ interface ScheduledTootActionListener {
class ScheduledTootAdapter( class ScheduledTootAdapter(
val listener: ScheduledTootActionListener val listener: ScheduledTootActionListener
) : PagedListAdapter<ScheduledStatus, ScheduledTootAdapter.TootViewHolder>( ) : PagedListAdapter<ScheduledStatus, BindingHolder<ItemScheduledTootBinding>>(
object: DiffUtil.ItemCallback<ScheduledStatus>(){ object: DiffUtil.ItemCallback<ScheduledStatus>(){
override fun areItemsTheSame(oldItem: ScheduledStatus, newItem: ScheduledStatus): Boolean { override fun areItemsTheSame(oldItem: ScheduledStatus, newItem: ScheduledStatus): Boolean {
return oldItem.id == newItem.id return oldItem.id == newItem.id
@ -46,40 +44,24 @@ class ScheduledTootAdapter(
} }
) { ) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TootViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemScheduledTootBinding> {
val view = LayoutInflater.from(parent.context) val binding = ItemScheduledTootBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.inflate(R.layout.item_scheduled_toot, parent, false) return BindingHolder(binding)
return TootViewHolder(view)
} }
override fun onBindViewHolder(viewHolder: TootViewHolder, position: Int) { override fun onBindViewHolder(holder: BindingHolder<ItemScheduledTootBinding>, position: Int) {
getItem(position)?.let{ getItem(position)?.let{ item ->
viewHolder.bind(it) holder.binding.edit.isEnabled = true
} holder.binding.delete.isEnabled = true
} holder.binding.text.text = item.params.text
holder.binding.edit.setOnClickListener { v: View ->
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 ->
v.isEnabled = false v.isEnabled = false
listener.edit(item) listener.edit(item)
} }
delete.setOnClickListener { v: View -> holder.binding.delete.setOnClickListener { v: View ->
v.isEnabled = false v.isEnabled = false
listener.delete(item) listener.delete(item)
} }
} }
} }
} }

View file

@ -19,24 +19,23 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.paging.PagedListAdapter import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.databinding.ItemHashtagBinding
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.HashtagViewHolder
import com.keylesspalace.tusky.entity.HashTag import com.keylesspalace.tusky.entity.HashTag
import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.BindingHolder
class SearchHashtagsAdapter(private val linkListener: LinkListener) 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 { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemHashtagBinding> {
val view = LayoutInflater.from(parent.context) val binding = ItemHashtagBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.inflate(R.layout.item_hashtag, parent, false) return BindingHolder(binding)
return HashtagViewHolder(view)
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: BindingHolder<ItemHashtagBinding>, position: Int) {
getItem(position)?.let { (name) -> 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.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
class BindingViewHolder<T : ViewBinding>( class BindingHolder<T : ViewBinding>(
val binding: T val binding: T
) : RecyclerView.ViewHolder(binding.root) ) : RecyclerView.ViewHolder(binding.root)

View file

@ -1,52 +1,69 @@
<?xml version="1.0" encoding="utf-8"?> <?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="72dp" android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingLeft="16dp" 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 <ImageView
android:id="@+id/avatar" android:id="@+id/avatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="24dp" android:layout_marginTop="10dp"
android:contentDescription="@string/action_view_profile" /> 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_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_toEndOf="@id/avatar" android:layout_marginStart="14dp"
android:layout_toStartOf="@id/acceptButton" android:ellipsize="end"
android:gravity="center_vertical" android:maxLines="1"
android:orientation="vertical"> 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 <TextView
android:id="@+id/displayNameTextView" android:id="@+id/usernameTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:layout_marginStart="14dp"
android:maxLines="1" android:ellipsize="end"
android:textColor="?android:textColorPrimary" android:maxLines="1"
android:textSize="?attr/status_text_large" android:textColor="?android:textColorSecondary"
android:textStyle="normal|bold" android:textSize="?attr/status_text_medium"
tools:text="Display name" /> app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toBottomOf="@id/displayNameTextView"
<TextView app:layout_constraintBottom_toBottomOf="@id/avatar"
android:id="@+id/usernameTextView" tools:text="\@username" />
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>
<ImageButton <ImageButton
android:id="@+id/acceptButton" android:id="@+id/acceptButton"
@ -55,10 +72,12 @@
android:layout_height="32dp" android:layout_height="32dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_toStartOf="@id/rejectButton" android:layout_marginTop="14dp"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_accept" android:contentDescription="@string/action_accept"
android:padding="4dp" android:padding="4dp"
app:layout_constraintEnd_toStartOf="@id/rejectButton"
app:layout_constraintTop_toBottomOf="@id/notificationTextView"
app:srcCompat="@drawable/ic_check_24dp" /> app:srcCompat="@drawable/ic_check_24dp" />
<ImageButton <ImageButton
@ -66,12 +85,14 @@
style="@style/TuskyImageButton" style="@style/TuskyImageButton"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginTop="14dp"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_reject" android:contentDescription="@string/action_reject"
android:padding="4dp" android:padding="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/notificationTextView"
app:srcCompat="@drawable/ic_reject_24dp" /> 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"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/hashtag"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="16dp" android:padding="16dp"

View file

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