Merge pull request #12996 from whistlingwoods/reapply-local-list-header-fix-by-j-haldane

This commit is contained in:
Stypox 2026-01-27 21:32:29 +01:00 committed by GitHub
commit 4ff958eef5
4 changed files with 37 additions and 29 deletions

View file

@ -28,6 +28,8 @@ import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
import static org.schabi.newpipe.util.ThemeHelper.getItemViewMode; import static org.schabi.newpipe.util.ThemeHelper.getItemViewMode;
import java.util.function.Supplier;
/** /**
* This fragment is design to be used with persistent data such as * This fragment is design to be used with persistent data such as
* {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained * {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained
@ -100,7 +102,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Nullable @Nullable
protected ViewBinding getListHeader() { protected Supplier<View> getListHeaderSupplier() {
return null; return null;
} }
@ -131,9 +133,9 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
itemsList = rootView.findViewById(R.id.items_list); itemsList = rootView.findViewById(R.id.items_list);
refreshItemViewMode(); refreshItemViewMode();
headerRootBinding = getListHeader(); final Supplier<View> listHeaderSupplier = getListHeaderSupplier();
if (headerRootBinding != null) { if (listHeaderSupplier != null) {
itemListAdapter.setHeader(headerRootBinding.getRoot()); itemListAdapter.setHeaderSupplier(listHeaderSupplier);
} }
footerRootBinding = getListFooter(); footerRootBinding = getListFooter();
itemListAdapter.setFooter(footerRootBinding.getRoot()); itemListAdapter.setFooter(footerRootBinding.getRoot());

View file

@ -37,6 +37,7 @@ import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle; import java.time.format.FormatStyle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
/* /*
* Created by Christian Schabesberger on 01.08.16. * Created by Christian Schabesberger on 01.08.16.
@ -88,7 +89,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
private final DateTimeFormatter dateTimeFormatter; private final DateTimeFormatter dateTimeFormatter;
private boolean showFooter = false; private boolean showFooter = false;
private View header = null; private Supplier<View> headerSupplier = null;
private View footer = null; private View footer = null;
private ItemViewMode itemViewMode = ItemViewMode.LIST; private ItemViewMode itemViewMode = ItemViewMode.LIST;
private boolean useItemHandle = false; private boolean useItemHandle = false;
@ -97,6 +98,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
recordManager = new HistoryRecordManager(context); recordManager = new HistoryRecordManager(context);
localItemBuilder = new LocalItemBuilder(context); localItemBuilder = new LocalItemBuilder(context);
localItems = new ArrayList<>(); localItems = new ArrayList<>();
dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Localization.getPreferredLocale(context)); .withLocale(Localization.getPreferredLocale(context));
} }
@ -124,7 +126,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + ", " Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + ", "
+ "localItems.size() = " + localItems.size() + ", " + "localItems.size() = " + localItems.size() + ", "
+ "header = " + header + ", footer = " + footer + ", " + "header = " + hasHeader() + ", footer = " + footer + ", "
+ "showFooter = " + showFooter); + "showFooter = " + showFooter);
} }
notifyItemRangeInserted(offsetStart, data.size()); notifyItemRangeInserted(offsetStart, data.size());
@ -144,7 +146,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
final int index = localItems.indexOf(data); final int index = localItems.indexOf(data);
if (index != -1) { if (index != -1) {
localItems.remove(index); localItems.remove(index);
notifyItemRemoved(index + (header != null ? 1 : 0)); notifyItemRemoved(index + (hasHeader() ? 1 : 0));
} else { } else {
// this happens when // this happens when
// 1) removeItem is called on infoItemDuplicate as in showStreamItemDialog of // 1) removeItem is called on infoItemDuplicate as in showStreamItemDialog of
@ -189,9 +191,9 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
this.useItemHandle = useItemHandle; this.useItemHandle = useItemHandle;
} }
public void setHeader(final View header) { public void setHeaderSupplier(@Nullable final Supplier<View> headerSupplier) {
final boolean changed = header != this.header; final boolean changed = headerSupplier != this.headerSupplier;
this.header = header; this.headerSupplier = headerSupplier;
if (changed) { if (changed) {
notifyDataSetChanged(); notifyDataSetChanged();
} }
@ -201,6 +203,10 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
this.footer = view; this.footer = view;
} }
protected boolean hasHeader() {
return this.headerSupplier != null;
}
public void showFooter(final boolean show) { public void showFooter(final boolean show) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "showFooter() called with: show = [" + show + "]"); Log.d(TAG, "showFooter() called with: show = [" + show + "]");
@ -218,11 +224,11 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
} }
private int adapterOffsetWithoutHeader(final int offset) { private int adapterOffsetWithoutHeader(final int offset) {
return offset - (header != null ? 1 : 0); return offset - (hasHeader() ? 1 : 0);
} }
private int sizeConsideringHeader() { private int sizeConsideringHeader() {
return localItems.size() + (header != null ? 1 : 0); return localItems.size() + (hasHeader() ? 1 : 0);
} }
public ArrayList<LocalItem> getItemsList() { public ArrayList<LocalItem> getItemsList() {
@ -232,7 +238,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
@Override @Override
public int getItemCount() { public int getItemCount() {
int count = localItems.size(); int count = localItems.size();
if (header != null) { if (hasHeader()) {
count++; count++;
} }
if (footer != null && showFooter) { if (footer != null && showFooter) {
@ -242,7 +248,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "getItemCount() called, count = " + count + ", " Log.d(TAG, "getItemCount() called, count = " + count + ", "
+ "localItems.size() = " + localItems.size() + ", " + "localItems.size() = " + localItems.size() + ", "
+ "header = " + header + ", footer = " + footer + ", " + "header = " + hasHeader() + ", footer = " + footer + ", "
+ "showFooter = " + showFooter); + "showFooter = " + showFooter);
} }
return count; return count;
@ -255,9 +261,9 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); Log.d(TAG, "getItemViewType() called with: position = [" + position + "]");
} }
if (header != null && position == 0) { if (hasHeader() && position == 0) {
return HEADER_TYPE; return HEADER_TYPE;
} else if (header != null) { } else if (hasHeader()) {
position--; position--;
} }
if (footer != null && position == localItems.size() && showFooter) { if (footer != null && position == localItems.size() && showFooter) {
@ -318,7 +324,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
} }
switch (type) { switch (type) {
case HEADER_TYPE: case HEADER_TYPE:
return new HeaderFooterHolder(header); return new HeaderFooterHolder(headerSupplier.get());
case FOOTER_TYPE: case FOOTER_TYPE:
return new HeaderFooterHolder(footer); return new HeaderFooterHolder(footer);
case LOCAL_PLAYLIST_HOLDER_TYPE: case LOCAL_PLAYLIST_HOLDER_TYPE:
@ -366,14 +372,14 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
if (holder instanceof LocalItemHolder) { if (holder instanceof LocalItemHolder) {
// If header isn't null, offset the items by -1 // If header isn't null, offset the items by -1
if (header != null) { if (hasHeader()) {
position--; position--;
} }
((LocalItemHolder) holder) ((LocalItemHolder) holder)
.updateFromItem(localItems.get(position), recordManager, dateTimeFormatter); .updateFromItem(localItems.get(position), recordManager, dateTimeFormatter);
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) { } else if (holder instanceof HeaderFooterHolder && position == 0 && hasHeader()) {
((HeaderFooterHolder) holder).view = header; ((HeaderFooterHolder) holder).view = headerSupplier.get();
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader() } else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
&& footer != null && showFooter) { && footer != null && showFooter) {
((HeaderFooterHolder) holder).view = footer; ((HeaderFooterHolder) holder).view = footer;
@ -387,10 +393,10 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
for (final Object payload : payloads) { for (final Object payload : payloads) {
if (payload instanceof StreamStateEntity) { if (payload instanceof StreamStateEntity) {
((LocalItemHolder) holder).updateState(localItems ((LocalItemHolder) holder).updateState(localItems
.get(header == null ? position : position - 1), recordManager); .get(hasHeader() ? position - 1 : position), recordManager);
} else if (payload instanceof Boolean) { } else if (payload instanceof Boolean) {
((LocalItemHolder) holder).updateState(localItems ((LocalItemHolder) holder).updateState(localItems
.get(header == null ? position : position - 1), recordManager); .get(hasHeader() ? position - 1 : position), recordManager);
} }
} }
} else { } else {

View file

@ -13,7 +13,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.viewbinding.ViewBinding;
import com.evernote.android.state.State; import com.evernote.android.state.State;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
@ -45,6 +44,7 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Supplier;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
@ -126,12 +126,12 @@ public class StatisticsPlaylistFragment
} }
@Override @Override
protected ViewBinding getListHeader() { protected Supplier<View> getListHeaderSupplier() {
headerBinding = StatisticPlaylistControlBinding.inflate(activity.getLayoutInflater(), headerBinding = StatisticPlaylistControlBinding.inflate(activity.getLayoutInflater(),
itemsList, false); itemsList, false);
playlistControlBinding = headerBinding.playlistControl; playlistControlBinding = headerBinding.playlistControl;
return headerBinding; return headerBinding::getRoot;
} }
@Override @Override

View file

@ -29,7 +29,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import com.evernote.android.state.State; import com.evernote.android.state.State;
import org.reactivestreams.Subscriber; import org.reactivestreams.Subscriber;
@ -67,6 +66,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
@ -158,14 +158,14 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} }
@Override @Override
protected ViewBinding getListHeader() { protected Supplier<View> getListHeaderSupplier() {
headerBinding = LocalPlaylistHeaderBinding.inflate(activity.getLayoutInflater(), itemsList, headerBinding = LocalPlaylistHeaderBinding.inflate(activity.getLayoutInflater(), itemsList,
false); false);
playlistControlBinding = headerBinding.playlistControl; playlistControlBinding = headerBinding.playlistControl;
headerBinding.playlistTitleView.setSelected(true); headerBinding.playlistTitleView.setSelected(true);
return headerBinding; return headerBinding::getRoot;
} }
@Override @Override