diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 3c2e65bb7..6ea0a8a0d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -17,10 +17,8 @@ import androidx.appcompat.app.ActionBar; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import androidx.viewbinding.ViewBinding; import org.schabi.newpipe.R; -import org.schabi.newpipe.databinding.PignateFooterBinding; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; @@ -44,6 +42,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Queue; +import java.util.function.Supplier; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.ktx.ViewUtils.animate; @@ -79,11 +78,6 @@ public abstract class BaseListFragment extends BaseStateFragment } } - @Override - public void onDetach() { - super.onDetach(); - } - @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -220,14 +214,10 @@ public abstract class BaseListFragment extends BaseStateFragment //////////////////////////////////////////////////////////////////////////*/ @Nullable - protected ViewBinding getListHeader() { + protected Supplier getListHeaderSupplier() { return null; } - protected ViewBinding getListFooter() { - return PignateFooterBinding.inflate(activity.getLayoutInflater(), itemsList, false); - } - protected RecyclerView.LayoutManager getListLayoutManager() { return new SuperScrollLayoutManager(activity); } @@ -252,11 +242,10 @@ public abstract class BaseListFragment extends BaseStateFragment itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); infoListAdapter.setUseGridVariant(useGrid); - infoListAdapter.setFooter(getListFooter().getRoot()); - final ViewBinding listHeader = getListHeader(); - if (listHeader != null) { - infoListAdapter.setHeader(listHeader.getRoot()); + final Supplier listHeaderSupplier = getListHeaderSupplier(); + if (listHeaderSupplier != null) { + infoListAdapter.setHeaderSupplier(listHeaderSupplier); } itemsList.setAdapter(infoListAdapter); @@ -271,7 +260,7 @@ public abstract class BaseListFragment extends BaseStateFragment @Override protected void initListeners() { super.initListeners(); - infoListAdapter.setOnStreamSelectedListener(new OnClickGesture() { + infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<>() { @Override public void selected(final StreamInfoItem selectedItem) { onStreamSelected(selectedItem); @@ -315,22 +304,98 @@ public abstract class BaseListFragment extends BaseStateFragment } }); - infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture() { + infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<>() { @Override public void selected(final CommentsInfoItem selectedItem) { onItemSelected(selectedItem); } }); + // Ensure that there is always a scroll listener (e.g. when rotating the device) + useNormalItemListScrollListener(); + } + + /** + * Removes all listeners and adds the normal scroll listener to the {@link #itemsList}. + */ + protected void useNormalItemListScrollListener() { + if (DEBUG) { + Log.d(TAG, "useNormalItemListScrollListener called"); + } itemsList.clearOnScrollListeners(); - itemsList.addOnScrollListener(new OnScrollBelowItemsListener() { + itemsList.addOnScrollListener(new DefaultItemListOnScrolledDownListener()); + } + + /** + * Removes all listeners and adds the initial scroll listener to the {@link #itemsList}. + *
+ * Which tries to load more items when not enough are in the view (not scrollable) + * and more are available. + *
+ * Note: This method only works because "This callback will also be called if visible + * item range changes after a layout calculation. In that case, dx and dy will be 0." + * - which might be unexpected because no actual scrolling occurs... + *
+ * This listener will be replaced by DefaultItemListOnScrolledDownListener when + *
    + *
  • the view was actually scrolled
  • + *
  • the view is scrollable
  • + *
  • no more items can be loaded
  • + *
+ */ + protected void useInitialItemListLoadScrollListener() { + if (DEBUG) { + Log.d(TAG, "useInitialItemListLoadScrollListener called"); + } + itemsList.clearOnScrollListeners(); + itemsList.addOnScrollListener(new DefaultItemListOnScrolledDownListener() { @Override - public void onScrolledDown(final RecyclerView recyclerView) { - onScrollToBottom(); + public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) { + super.onScrolled(recyclerView, dx, dy); + + if (dy != 0) { + log("Vertical scroll occurred"); + + useNormalItemListScrollListener(); + return; + } + if (isLoading.get()) { + log("Still loading data -> Skipping"); + return; + } + if (!hasMoreItems()) { + log("No more items to load"); + + useNormalItemListScrollListener(); + return; + } + if (itemsList.canScrollVertically(1) + || itemsList.canScrollVertically(-1)) { + log("View is scrollable"); + + useNormalItemListScrollListener(); + return; + } + + log("Loading more data"); + loadMoreItems(); + } + + private void log(final String msg) { + if (DEBUG) { + Log.d(TAG, "initItemListLoadScrollListener - " + msg); + } } }); } + class DefaultItemListOnScrolledDownListener extends OnScrollBelowItemsListener { + @Override + public void onScrolledDown(final RecyclerView recyclerView) { + onScrollToBottom(); + } + } + private void onStreamSelected(final StreamInfoItem selectedItem) { onItemSelected(selectedItem); NavigationHelper.openVideoDetailFragment(requireContext(), getFM(), @@ -418,6 +483,12 @@ public abstract class BaseListFragment extends BaseStateFragment // Load and handle //////////////////////////////////////////////////////////////////////////*/ + @Override + protected void startLoading(final boolean forceLoad) { + useInitialItemListLoadScrollListener(); + super.startLoading(forceLoad); + } + protected abstract void loadMoreItems(); protected abstract boolean hasMoreItems(); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index e98dc9fda..ebd586e35 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -65,7 +65,7 @@ public abstract class BaseListInfoFragment super.onResume(); // Check if it was loading when the fragment was stopped/paused, if (wasLoading.getAndSet(false)) { - if (hasMoreItems() && infoListAdapter.getItemsList().size() > 0) { + if (hasMoreItems() && !infoListAdapter.getItemsList().isEmpty()) { loadMoreItems(); } else { doInitialLoadLogic(); @@ -105,6 +105,7 @@ public abstract class BaseListInfoFragment // Load and handle //////////////////////////////////////////////////////////////////////////*/ + @Override protected void doInitialLoadLogic() { if (DEBUG) { Log.d(TAG, "doInitialLoadLogic() called"); @@ -158,6 +159,7 @@ public abstract class BaseListInfoFragment */ protected abstract Single loadMoreItemsLogic(); + @Override protected void loadMoreItems() { isLoading.set(true); @@ -171,9 +173,9 @@ public abstract class BaseListInfoFragment .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doFinally(this::allowDownwardFocusScroll) - .subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> { + .subscribe(infoItemsPage -> { isLoading.set(false); - handleNextItems(InfoItemsPage); + handleNextItems(infoItemsPage); }, (@NonNull Throwable throwable) -> dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(throwable, errorUserAction, "Loading more items: " + url, serviceId))); @@ -223,7 +225,7 @@ public abstract class BaseListInfoFragment setTitle(name); if (infoListAdapter.getItemsList().isEmpty()) { - if (result.getRelatedItems().size() > 0) { + if (!result.getRelatedItems().isEmpty()) { infoListAdapter.addInfoItemList(result.getRelatedItems()); showListFooter(hasMoreItems()); } else { @@ -240,7 +242,7 @@ public abstract class BaseListInfoFragment final List errors = new ArrayList<>(result.getErrors()); // handling ContentNotSupportedException not to show the error but an appropriate string // so that crashes won't be sent uselessly and the user will understand what happened - errors.removeIf(throwable -> throwable instanceof ContentNotSupportedException); + errors.removeIf(ContentNotSupportedException.class::isInstance); if (!errors.isEmpty()) { dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(), diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 37954478d..918facc4e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.fragments.list.channel; +import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor; + import android.content.Context; import android.os.Bundle; import android.text.TextUtils; @@ -17,7 +21,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.core.content.ContextCompat; -import androidx.viewbinding.ViewBinding; import com.jakewharton.rxbinding4.view.RxView; @@ -29,7 +32,6 @@ import org.schabi.newpipe.databinding.PlaylistControlBinding; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; @@ -43,13 +45,14 @@ import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.util.external_communication.ShareUtils; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Collectors; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; @@ -61,10 +64,6 @@ import io.reactivex.rxjava3.functions.Consumer; import io.reactivex.rxjava3.functions.Function; import io.reactivex.rxjava3.schedulers.Schedulers; -import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor; - public class ChannelFragment extends BaseListInfoFragment implements View.OnClickListener { @@ -145,12 +144,12 @@ public class ChannelFragment extends BaseListInfoFragment //////////////////////////////////////////////////////////////////////////*/ @Override - protected ViewBinding getListHeader() { + protected Supplier getListHeaderSupplier() { headerBinding = ChannelHeaderBinding .inflate(activity.getLayoutInflater(), itemsList, false); playlistControlBinding = headerBinding.playlistControl; - return headerBinding; + return headerBinding::getRoot; } @Override @@ -183,13 +182,6 @@ public class ChannelFragment extends BaseListInfoFragment } } - private void openRssFeed() { - final ChannelInfo info = currentInfo; - if (info != null) { - ShareUtils.openUrlInBrowser(requireContext(), info.getFeedUrl(), false); - } - } - @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { @@ -197,7 +189,10 @@ public class ChannelFragment extends BaseListInfoFragment NavigationHelper.openSettings(requireContext()); break; case R.id.menu_item_rss: - openRssFeed(); + if (currentInfo != null) { + ShareUtils.openUrlInBrowser( + requireContext(), currentInfo.getFeedUrl(), false); + } break; case R.id.menu_item_openInBrowser: if (currentInfo != null) { @@ -516,12 +511,11 @@ public class ChannelFragment extends BaseListInfoFragment } private PlayQueue getPlayQueue(final int index) { - final List streamItems = new ArrayList<>(); - for (final InfoItem i : infoListAdapter.getItemsList()) { - if (i instanceof StreamInfoItem) { - streamItems.add((StreamInfoItem) i); - } - } + final List streamItems = infoListAdapter.getItemsList().stream() + .filter(StreamInfoItem.class::isInstance) + .map(StreamInfoItem.class::cast) + .collect(Collectors.toList()); + return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(), currentInfo.getNextPage(), streamItems, index); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 640d08064..84dcb4fd9 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.fragments.list.playlist; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; + import android.app.Activity; import android.content.Context; import android.os.Bundle; @@ -15,7 +19,6 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; -import androidx.viewbinding.ViewBinding; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; @@ -42,17 +45,18 @@ import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.util.ExtractorHelper; -import org.schabi.newpipe.util.PicassoHelper; -import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.StreamDialogEntry; +import org.schabi.newpipe.util.external_communication.KoreUtils; +import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Flowable; @@ -60,10 +64,6 @@ import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; - public class PlaylistFragment extends BaseListInfoFragment { private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG"; @@ -120,12 +120,12 @@ public class PlaylistFragment extends BaseListInfoFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - protected ViewBinding getListHeader() { + protected Supplier getListHeaderSupplier() { headerBinding = PlaylistHeaderBinding .inflate(activity.getLayoutInflater(), itemsList, false); playlistControlBinding = headerBinding.playlistControl; - return headerBinding; + return headerBinding::getRoot; } @Override @@ -413,7 +413,7 @@ public class PlaylistFragment extends BaseListInfoFragment { } private Subscriber> getPlaylistBookmarkSubscriber() { - return new Subscriber>() { + return new Subscriber<>() { @Override public void onSubscribe(final Subscription s) { if (bookmarkReactor != null) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java index 6532417c0..7ba6aa2ab 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.fragments.list.videos; -import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; @@ -12,7 +11,6 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; -import androidx.viewbinding.ViewBinding; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.RelatedItemsHeaderBinding; @@ -24,14 +22,14 @@ import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.util.RelatedItemInfo; import java.io.Serializable; +import java.util.function.Supplier; import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.disposables.CompositeDisposable; public class RelatedItemsFragment extends BaseListInfoFragment implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String INFO_KEY = "related_info_key"; - private final CompositeDisposable disposables = new CompositeDisposable(); + private RelatedItemInfo relatedItemInfo; /*////////////////////////////////////////////////////////////////////////// @@ -54,11 +52,6 @@ public class RelatedItemsFragment extends BaseListInfoFragment // LifeCycle //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onAttach(@NonNull final Context context) { - super.onAttach(context); - } - @Override public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @@ -66,12 +59,6 @@ public class RelatedItemsFragment extends BaseListInfoFragment return inflater.inflate(R.layout.fragment_related_items, container, false); } - @Override - public void onDestroy() { - super.onDestroy(); - disposables.clear(); - } - @Override public void onDestroyView() { headerBinding = null; @@ -79,22 +66,23 @@ public class RelatedItemsFragment extends BaseListInfoFragment } @Override - protected ViewBinding getListHeader() { - if (relatedItemInfo != null && relatedItemInfo.getRelatedItems() != null) { - headerBinding = RelatedItemsHeaderBinding - .inflate(activity.getLayoutInflater(), itemsList, false); - - final SharedPreferences pref = PreferenceManager - .getDefaultSharedPreferences(requireContext()); - final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false); - headerBinding.autoplaySwitch.setChecked(autoplay); - headerBinding.autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) -> - PreferenceManager.getDefaultSharedPreferences(requireContext()).edit() - .putBoolean(getString(R.string.auto_queue_key), b).apply()); - return headerBinding; - } else { + protected Supplier getListHeaderSupplier() { + if (relatedItemInfo == null || relatedItemInfo.getRelatedItems() == null) { return null; } + + headerBinding = RelatedItemsHeaderBinding + .inflate(activity.getLayoutInflater(), itemsList, false); + + final SharedPreferences pref = PreferenceManager + .getDefaultSharedPreferences(requireContext()); + final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false); + headerBinding.autoplaySwitch.setChecked(autoplay); + headerBinding.autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) -> + PreferenceManager.getDefaultSharedPreferences(requireContext()).edit() + .putBoolean(getString(R.string.auto_queue_key), b).apply()); + + return headerBinding::getRoot; } @Override @@ -128,7 +116,6 @@ public class RelatedItemsFragment extends BaseListInfoFragment } ViewUtils.slideUp(requireView(), 120, 96, 0.06f); - disposables.clear(); } /*////////////////////////////////////////////////////////////////////////// @@ -137,11 +124,13 @@ public class RelatedItemsFragment extends BaseListInfoFragment @Override public void setTitle(final String title) { + // Nothing to do - override parent } @Override public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { + // Nothing to do - override parent } private void setInitialData(final StreamInfo info) { @@ -169,11 +158,10 @@ public class RelatedItemsFragment extends BaseListInfoFragment @Override public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String s) { - final SharedPreferences pref = - PreferenceManager.getDefaultSharedPreferences(requireContext()); - final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false); if (headerBinding != null) { - headerBinding.autoplaySwitch.setChecked(autoplay); + headerBinding.autoplaySwitch.setChecked( + sharedPreferences.getBoolean( + getString(R.string.auto_queue_key), false)); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java index 56bc63384..fb27593e7 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java @@ -2,6 +2,7 @@ package org.schabi.newpipe.info_list; import android.content.Context; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -10,7 +11,7 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import org.schabi.newpipe.database.stream.model.StreamStateEntity; +import org.schabi.newpipe.databinding.PignateFooterBinding; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; @@ -34,6 +35,7 @@ import org.schabi.newpipe.util.OnClickGesture; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; /* * Created by Christian Schabesberger on 01.08.16. @@ -74,18 +76,20 @@ public class InfoListAdapter extends RecyclerView.Adapter infoItemList; + private final List infoItemList; private final HistoryRecordManager recordManager; private boolean useMiniVariant = false; private boolean useGridVariant = false; private boolean showFooter = false; - private View header = null; - private View footer = null; + + private Supplier headerSupplier = null; public InfoListAdapter(final Context context) { - this.recordManager = new HistoryRecordManager(context); + layoutInflater = LayoutInflater.from(context); + recordManager = new HistoryRecordManager(context); infoItemBuilder = new InfoItemBuilder(context); infoItemList = new ArrayList<>(); } @@ -129,12 +133,12 @@ public class InfoListAdapter extends RecyclerView.Adapter offsetStart = " + offsetStart + ", " + "infoItemList.size() = " + infoItemList.size() + ", " - + "header = " + header + ", footer = " + footer + ", " + + "hasHeader = " + hasHeader() + ", " + "showFooter = " + showFooter); } notifyItemRangeInserted(offsetStart, data.size()); - if (footer != null && showFooter) { + if (showFooter) { final int footerNow = sizeConsideringHeaderOffset(); notifyItemMoved(offsetStart, footerNow); @@ -145,43 +149,6 @@ public class InfoListAdapter extends RecyclerView.Adapter data) { - infoItemList.clear(); - infoItemList.addAll(data); - notifyDataSetChanged(); - } - - public void addInfoItem(@Nullable final InfoItem data) { - if (data == null) { - return; - } - if (DEBUG) { - Log.d(TAG, "addInfoItem() before > infoItemList.size() = " - + infoItemList.size() + ", thread = " + Thread.currentThread()); - } - - final int positionInserted = sizeConsideringHeaderOffset(); - infoItemList.add(data); - - if (DEBUG) { - Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", " - + "infoItemList.size() = " + infoItemList.size() + ", " - + "header = " + header + ", footer = " + footer + ", " - + "showFooter = " + showFooter); - } - notifyItemInserted(positionInserted); - - if (footer != null && showFooter) { - final int footerNow = sizeConsideringHeaderOffset(); - notifyItemMoved(positionInserted, footerNow); - - if (DEBUG) { - Log.d(TAG, "addInfoItem() footer from " + positionInserted - + " to " + footerNow); - } - } - } - public void clearStreamItemList() { if (infoItemList.isEmpty()) { return; @@ -190,16 +157,16 @@ public class InfoListAdapter extends RecyclerView.Adapter headerSupplier) { + final boolean changed = headerSupplier != this.headerSupplier; + this.headerSupplier = headerSupplier; if (changed) { notifyDataSetChanged(); } } - public void setFooter(final View view) { - this.footer = view; + protected boolean hasHeader() { + return this.headerSupplier != null; } public void showFooter(final boolean show) { @@ -219,48 +186,49 @@ public class InfoListAdapter extends RecyclerView.Adapter getItemsList() { + public List getItemsList() { return infoItemList; } @Override public int getItemCount() { int count = infoItemList.size(); - if (header != null) { + if (hasHeader()) { count++; } - if (footer != null && showFooter) { + if (showFooter) { count++; } if (DEBUG) { Log.d(TAG, "getItemCount() called with: " + "count = " + count + ", infoItemList.size() = " + infoItemList.size() + ", " - + "header = " + header + ", footer = " + footer + ", " + + "hasHeader = " + hasHeader() + ", " + "showFooter = " + showFooter); } return count; } + @SuppressWarnings("FinalParameters") @Override public int getItemViewType(int position) { if (DEBUG) { Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); } - if (header != null && position == 0) { + if (hasHeader() && position == 0) { return HEADER_TYPE; - } else if (header != null) { + } else if (hasHeader()) { position--; } - if (footer != null && position == infoItemList.size() && showFooter) { + if (position == infoItemList.size() && showFooter) { return FOOTER_TYPE; } final InfoItem item = infoItemList.get(position); @@ -290,10 +258,16 @@ public class InfoListAdapter extends RecyclerView.Adapter payloads) { - if (!payloads.isEmpty() && holder instanceof InfoItemHolder) { - for (final Object payload : payloads) { - if (payload instanceof StreamStateEntity) { - ((InfoItemHolder) holder).updateState(infoItemList - .get(header == null ? position : position - 1), recordManager); - } else if (payload instanceof Boolean) { - ((InfoItemHolder) holder).updateState(infoItemList - .get(header == null ? position : position - 1), recordManager); - } - } - } else { - onBindViewHolder(holder, position); + ((InfoItemHolder) holder).updateFromItem( + // If header is present, offset the items by -1 + infoItemList.get(hasHeader() ? position - 1 : position), recordManager); } } @@ -371,12 +320,9 @@ public class InfoListAdapter extends RecyclerView.Adapter - -