mirror of
https://github.com/tuskyapp/Tusky
synced 2025-02-09 20:40:34 +01:00
split out FilteredStatusViewHolder from StatusBaseViewHolder (#4543)
This is way more efficient than before as less views need to be inflated and bound for a filtered status to be rendered. It also should fix the bug where sometimes a `StatusViewHolder` that is set up for showing a status gets bound to a status that is filtered, leading to a crash.
This commit is contained in:
parent
8a57bcc3f4
commit
326676a9c6
@ -0,0 +1,57 @@
|
||||
/* Copyright 2024 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 androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.notifications.NotificationsViewHolder
|
||||
import com.keylesspalace.tusky.databinding.ItemStatusFilteredBinding
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.FilterResult
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.viewdata.NotificationViewData
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
|
||||
class FilteredStatusViewHolder(
|
||||
private val binding: ItemStatusFilteredBinding,
|
||||
listener: StatusActionListener
|
||||
) : NotificationsViewHolder, RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
init {
|
||||
binding.statusFilterShowAnyway.setOnClickListener {
|
||||
listener.clearWarningAction(bindingAdapterPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun bind(
|
||||
viewData: NotificationViewData.Concrete,
|
||||
payloads: List<*>,
|
||||
statusDisplayOptions: StatusDisplayOptions
|
||||
) = bind(viewData.statusViewData!!)
|
||||
|
||||
fun bind(viewData: StatusViewData.Concrete) {
|
||||
val matchedFilterResult: FilterResult? = viewData.actionable.filtered.orEmpty().find { filterResult ->
|
||||
filterResult.filter.action == Filter.Action.WARN
|
||||
}
|
||||
|
||||
val matchedFilterTitle = matchedFilterResult?.filter?.title.orEmpty()
|
||||
|
||||
binding.statusFilterLabel.text = itemView.context.getString(
|
||||
R.string.status_filter_placeholder_label_format,
|
||||
matchedFilterTitle
|
||||
)
|
||||
}
|
||||
}
|
@ -45,8 +45,6 @@ import com.keylesspalace.tusky.entity.Attachment.Focus;
|
||||
import com.keylesspalace.tusky.entity.Attachment.MetaData;
|
||||
import com.keylesspalace.tusky.entity.Card;
|
||||
import com.keylesspalace.tusky.entity.Emoji;
|
||||
import com.keylesspalace.tusky.entity.Filter;
|
||||
import com.keylesspalace.tusky.entity.FilterResult;
|
||||
import com.keylesspalace.tusky.entity.HashTag;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.entity.Translation;
|
||||
@ -120,9 +118,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView cardDescription;
|
||||
private final TextView cardUrl;
|
||||
private final PollAdapter pollAdapter;
|
||||
protected final LinearLayout filteredPlaceholder;
|
||||
protected final TextView filteredPlaceholderLabel;
|
||||
protected final Button filteredPlaceholderShowButton;
|
||||
protected final ConstraintLayout statusContainer;
|
||||
private final TextView translationStatusView;
|
||||
private final Button untranslateButton;
|
||||
@ -179,9 +174,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||
cardDescription = itemView.findViewById(R.id.card_description);
|
||||
cardUrl = itemView.findViewById(R.id.card_link);
|
||||
|
||||
filteredPlaceholder = itemView.findViewById(R.id.status_filtered_placeholder);
|
||||
filteredPlaceholderLabel = itemView.findViewById(R.id.status_filter_label);
|
||||
filteredPlaceholderShowButton = itemView.findViewById(R.id.status_filter_show_anyway);
|
||||
statusContainer = itemView.findViewById(R.id.status_container);
|
||||
|
||||
pollAdapter = new PollAdapter();
|
||||
@ -822,8 +814,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
setSpoilerAndContent(status, statusDisplayOptions, listener);
|
||||
|
||||
setupFilterPlaceholder(status, listener, statusDisplayOptions);
|
||||
|
||||
setDescriptionForStatus(status, statusDisplayOptions);
|
||||
|
||||
// Workaround for RecyclerView 1.0.0 / androidx.core 1.0.0
|
||||
@ -866,35 +856,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||
}
|
||||
}
|
||||
|
||||
private void setupFilterPlaceholder(StatusViewData.Concrete status, StatusActionListener listener, StatusDisplayOptions displayOptions) {
|
||||
if (status.getFilterAction() != Filter.Action.WARN) {
|
||||
showFilteredPlaceholder(false);
|
||||
return;
|
||||
}
|
||||
|
||||
showFilteredPlaceholder(true);
|
||||
|
||||
Filter matchedFilter = null;
|
||||
|
||||
for (FilterResult result : status.getActionable().getFiltered()) {
|
||||
Filter filter = result.getFilter();
|
||||
if (filter.getAction() == Filter.Action.WARN) {
|
||||
matchedFilter = filter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final String matchedFilterTitle;
|
||||
if (matchedFilter == null) {
|
||||
matchedFilterTitle = "";
|
||||
} else {
|
||||
matchedFilterTitle = matchedFilter.getTitle();
|
||||
}
|
||||
|
||||
filteredPlaceholderLabel.setText(itemView.getContext().getString(R.string.status_filter_placeholder_label_format, matchedFilterTitle));
|
||||
filteredPlaceholderShowButton.setOnClickListener(view -> listener.clearWarningAction(getBindingAdapterPosition()));
|
||||
}
|
||||
|
||||
protected static boolean hasPreviewableAttachment(@NonNull List<Attachment> attachments) {
|
||||
for (Attachment attachment : attachments) {
|
||||
if (attachment.getType() == Attachment.Type.AUDIO || attachment.getType() == Attachment.Type.UNKNOWN) {
|
||||
@ -1306,13 +1267,4 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||
bookmarkButton.setVisibility(visibility);
|
||||
moreButton.setVisibility(visibility);
|
||||
}
|
||||
|
||||
public void showFilteredPlaceholder(boolean show) {
|
||||
if (statusContainer != null) {
|
||||
statusContainer.setVisibility(show ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
if (filteredPlaceholder != null) {
|
||||
filteredPlaceholder.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,16 +20,17 @@ import android.view.ViewGroup
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.FilteredStatusViewHolder
|
||||
import com.keylesspalace.tusky.adapter.FollowRequestViewHolder
|
||||
import com.keylesspalace.tusky.adapter.PlaceholderViewHolder
|
||||
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
|
||||
import com.keylesspalace.tusky.databinding.ItemFollowBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemReportNotificationBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemStatusBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemStatusFilteredBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemStatusNotificationBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemStatusPlaceholderBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemStatusWrapperBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemUnknownNotificationBinding
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
@ -103,14 +104,13 @@ class NotificationsPagingAdapter(
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
VIEW_TYPE_STATUS -> StatusViewHolder(
|
||||
ItemStatusBinding.inflate(inflater, parent, false).root,
|
||||
inflater.inflate(R.layout.item_status, parent, false),
|
||||
statusListener,
|
||||
accountId
|
||||
)
|
||||
VIEW_TYPE_STATUS_FILTERED -> StatusViewHolder(
|
||||
ItemStatusWrapperBinding.inflate(inflater, parent, false).root,
|
||||
statusListener,
|
||||
accountId
|
||||
VIEW_TYPE_STATUS_FILTERED -> FilteredStatusViewHolder(
|
||||
ItemStatusFilteredBinding.inflate(inflater, parent, false),
|
||||
statusListener
|
||||
)
|
||||
VIEW_TYPE_STATUS_NOTIFICATION -> StatusNotificationViewHolder(
|
||||
ItemStatusNotificationBinding.inflate(inflater, parent, false),
|
||||
|
@ -158,7 +158,7 @@ class NotificationsViewModel @Inject constructor(
|
||||
|
||||
private fun shouldFilterStatus(notificationViewData: NotificationViewData): Filter.Action {
|
||||
return when ((notificationViewData as? NotificationViewData.Concrete)?.type) {
|
||||
Notification.Type.MENTION, Notification.Type.STATUS, Notification.Type.POLL -> {
|
||||
Notification.Type.MENTION, Notification.Type.POLL -> {
|
||||
notificationViewData.statusViewData?.let { statusViewData ->
|
||||
statusViewData.filterAction = filterModel.shouldFilterStatus(statusViewData.actionable)
|
||||
return statusViewData.filterAction
|
||||
|
@ -21,9 +21,11 @@ import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.FilteredStatusViewHolder
|
||||
import com.keylesspalace.tusky.adapter.PlaceholderViewHolder
|
||||
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
|
||||
import com.keylesspalace.tusky.adapter.StatusViewHolder
|
||||
import com.keylesspalace.tusky.databinding.ItemStatusFilteredBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemStatusPlaceholderBinding
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
@ -51,7 +53,10 @@ class TimelinePagingAdapter(
|
||||
val inflater = LayoutInflater.from(viewGroup.context)
|
||||
return when (viewType) {
|
||||
VIEW_TYPE_STATUS_FILTERED -> {
|
||||
StatusViewHolder(inflater.inflate(R.layout.item_status_wrapper, viewGroup, false))
|
||||
FilteredStatusViewHolder(
|
||||
ItemStatusFilteredBinding.inflate(inflater, viewGroup, false),
|
||||
statusListener
|
||||
)
|
||||
}
|
||||
VIEW_TYPE_PLACEHOLDER -> {
|
||||
PlaceholderViewHolder(
|
||||
@ -82,18 +87,23 @@ class TimelinePagingAdapter(
|
||||
position: Int,
|
||||
payloads: List<*>?
|
||||
) {
|
||||
val status = getItem(position)
|
||||
if (status is StatusViewData.Placeholder) {
|
||||
val viewData = getItem(position)
|
||||
if (viewData is StatusViewData.Placeholder) {
|
||||
val holder = viewHolder as PlaceholderViewHolder
|
||||
holder.setup(status.isLoading)
|
||||
} else if (status is StatusViewData.Concrete) {
|
||||
val holder = viewHolder as StatusViewHolder
|
||||
holder.setupWithStatus(
|
||||
status,
|
||||
statusListener,
|
||||
statusDisplayOptions,
|
||||
if (payloads != null && payloads.isNotEmpty()) payloads[0] else null
|
||||
)
|
||||
holder.setup(viewData.isLoading)
|
||||
} else if (viewData is StatusViewData.Concrete) {
|
||||
if (viewData.filterAction == Filter.Action.WARN) {
|
||||
val holder = viewHolder as FilteredStatusViewHolder
|
||||
holder.bind(viewData)
|
||||
} else {
|
||||
val holder = viewHolder as StatusViewHolder
|
||||
holder.setupWithStatus(
|
||||
viewData,
|
||||
statusListener,
|
||||
statusDisplayOptions,
|
||||
if (payloads != null && payloads.isNotEmpty()) payloads[0] else null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,10 +19,13 @@ import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.FilteredStatusViewHolder
|
||||
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
|
||||
import com.keylesspalace.tusky.adapter.StatusDetailedViewHolder
|
||||
import com.keylesspalace.tusky.adapter.StatusViewHolder
|
||||
import com.keylesspalace.tusky.databinding.ItemStatusFilteredBinding
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
@ -31,29 +34,33 @@ import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
class ThreadAdapter(
|
||||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val statusActionListener: StatusActionListener
|
||||
) : ListAdapter<StatusViewData.Concrete, StatusBaseViewHolder>(ThreadDifferCallback) {
|
||||
) : ListAdapter<StatusViewData.Concrete, RecyclerView.ViewHolder>(ThreadDifferCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatusBaseViewHolder {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
VIEW_TYPE_STATUS -> {
|
||||
VIEW_TYPE_STATUS ->
|
||||
StatusViewHolder(inflater.inflate(R.layout.item_status, parent, false))
|
||||
}
|
||||
VIEW_TYPE_STATUS_FILTERED -> {
|
||||
StatusViewHolder(inflater.inflate(R.layout.item_status_wrapper, parent, false))
|
||||
}
|
||||
VIEW_TYPE_STATUS_DETAILED -> {
|
||||
VIEW_TYPE_STATUS_FILTERED ->
|
||||
FilteredStatusViewHolder(
|
||||
ItemStatusFilteredBinding.inflate(inflater, parent, false),
|
||||
statusActionListener
|
||||
)
|
||||
VIEW_TYPE_STATUS_DETAILED ->
|
||||
StatusDetailedViewHolder(
|
||||
inflater.inflate(R.layout.item_status_detailed, parent, false)
|
||||
)
|
||||
}
|
||||
else -> error("Unknown item type: $viewType")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: StatusBaseViewHolder, position: Int) {
|
||||
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
|
||||
val status = getItem(position)
|
||||
viewHolder.setupWithStatus(status, statusActionListener, statusDisplayOptions)
|
||||
if (viewHolder is FilteredStatusViewHolder) {
|
||||
viewHolder.bind(status)
|
||||
} else if (viewHolder is StatusBaseViewHolder) {
|
||||
viewHolder.setupWithStatus(status, statusActionListener, statusDisplayOptions)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
@ -68,7 +75,6 @@ class ThreadAdapter(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ThreadAdapter"
|
||||
private const val VIEW_TYPE_STATUS = 0
|
||||
private const val VIEW_TYPE_STATUS_DETAILED = 1
|
||||
private const val VIEW_TYPE_STATUS_FILTERED = 2
|
||||
|
@ -3,7 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/status_filtered_placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
@ -12,11 +12,10 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:text="Filter: MyFilter"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
tools:ignore="HardcodedText" />
|
||||
tools:text="Filter: MyFilter" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/status_filter_show_anyway"
|
||||
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include layout="@layout/item_status" />
|
||||
|
||||
<include
|
||||
layout="@layout/item_status_filtered"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
</FrameLayout>
|
Loading…
x
Reference in New Issue
Block a user