Skip to content

Commit

Permalink
Add lazy loading to feed item list (#7091)
Browse files Browse the repository at this point in the history
  • Loading branch information
ByteHamster committed Apr 13, 2024
1 parent 04fab47 commit f3bca9d
Show file tree
Hide file tree
Showing 18 changed files with 167 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private void bind(DownloadLogItemViewHolder holder, DownloadResult status, int p
if (status.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
holder.secondaryActionButton.setOnClickListener(v -> {
holder.secondaryActionButton.setVisibility(View.INVISIBLE);
Feed feed = DBReader.getFeed(status.getFeedfileId());
Feed feed = DBReader.getFeed(status.getFeedfileId(), false, 0, 0);
if (feed == null) {
Log.e(TAG, "Could not find feed for feed id: " + status.getFeedfileId());
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public DownloadLogDetailsDialog(@NonNull Context context, DownloadResult status)
url = media.getDownloadUrl();
}
} else if (status.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
Feed feed = DBReader.getFeed(status.getFeedfileId());
Feed feed = DBReader.getFeed(status.getFeedfileId(), false, 0, 0);
if (feed != null) {
url = feed.getDownloadUrl();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ protected void doTint(Context themedContext) {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
long feedId = getArguments().getLong(EXTRA_FEED_ID);
disposable = Maybe.create((MaybeOnSubscribe<Feed>) emitter -> {
Feed feed = DBReader.getFeed(feedId);
Feed feed = DBReader.getFeed(feedId, false, 0, 0);
if (feed != null) {
emitter.onSuccess(feed);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;

Expand Down Expand Up @@ -49,7 +50,6 @@
import de.danoeh.antennapod.ui.MenuItemUtils;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.database.FeedItemPermutors;
import de.danoeh.antennapod.ui.common.IntentUtils;
import de.danoeh.antennapod.ui.share.ShareUtils;
import de.danoeh.antennapod.ui.episodeslist.MoreContentListFooterUtil;
Expand All @@ -73,7 +73,6 @@
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.ui.glide.FastBlurTransformation;
import de.danoeh.antennapod.ui.episodeslist.EpisodeItemViewHolder;
Expand All @@ -91,6 +90,10 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
public static final String TAG = "ItemlistFragment";
private static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
private static final String KEY_UP_ARROW = "up_arrow";
protected static final int EPISODES_PER_PAGE = 150;
protected int page = 1;
protected boolean isLoadingMore = false;
protected boolean hasMoreItems = false;

private FeedItemListAdapter adapter;
private SwipeActions swipeActions;
Expand Down Expand Up @@ -147,6 +150,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
}
((MainActivity) getActivity()).setupToolbarToggle(viewBinding.toolbar, displayUpArrow);
updateToolbar();
setupLoadMoreScrollListener();

viewBinding.recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
adapter = new FeedItemListAdapter((MainActivity) getActivity());
Expand Down Expand Up @@ -317,6 +321,21 @@ public boolean onContextItemSelected(@NonNull MenuItem item) {
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}

private void setupLoadMoreScrollListener() {
viewBinding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView view, int deltaX, int deltaY) {
super.onScrolled(view, deltaX, deltaY);
if (!isLoadingMore && hasMoreItems && viewBinding.recyclerView.isScrolledToBottom()) {
/* The end of the list has been reached. Load more data. */
page++;
loadMoreItems();
isLoadingMore = true;
}
}
});
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
MainActivity activity = (MainActivity) getActivity();
Expand Down Expand Up @@ -535,17 +554,23 @@ private void loadItems() {
if (disposable != null) {
disposable.dispose();
}
disposable = Observable.fromCallable(this::loadData)
disposable = Observable.fromCallable(
() -> {
feed = DBReader.getFeed(feedID, true, 0, page * EPISODES_PER_PAGE);
int count = DBReader.getFeedEpisodeCount(feed.getId(), feed.getItemFilter());
return new Pair<>(feed, count);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
result -> {
feed = result;
hasMoreItems = !(page == 1 && feed.getItems().size() < EPISODES_PER_PAGE);
swipeActions.setFilter(feed.getItemFilter());
refreshHeaderView();
viewBinding.progressBar.setVisibility(View.GONE);
adapter.setDummyViews(0);
adapter.updateItems(feed.getItems());
adapter.setTotalNumberOfItems(result.second);
updateToolbar();
}, error -> {
feed = null;
Expand All @@ -557,19 +582,37 @@ private void loadItems() {
});
}

@Nullable
private Feed loadData() {
Feed feed = DBReader.getFeed(feedID, true);
if (feed == null) {
return null;
}
DBReader.loadAdditionalFeedItemListData(feed.getItems());
if (feed.getSortOrder() != null) {
List<FeedItem> feedItems = feed.getItems();
FeedItemPermutors.getPermutor(feed.getSortOrder()).reorder(feedItems);
feed.setItems(feedItems);
private void loadMoreItems() {
if (disposable != null) {
disposable.dispose();
}
return feed;
isLoadingMore = true;
adapter.setDummyViews(1);
adapter.notifyItemInserted(adapter.getItemCount() - 1);
disposable = Observable.fromCallable(() -> DBReader.getFeed(feedID, true,
(page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
data -> {
if (data.getItems().size() < EPISODES_PER_PAGE) {
hasMoreItems = false;
}
feed.getItems().addAll(data.getItems());
adapter.setDummyViews(0);
adapter.updateItems(feed.getItems());
if (adapter.shouldSelectLazyLoadedItems()) {
adapter.setSelected(feed.getItems().size() - data.getItems().size(),
feed.getItems().size(), true);
}
}, error -> {
adapter.setDummyViews(0);
adapter.updateItems(Collections.emptyList());
Log.e(TAG, Log.getStackTraceString(error));
}, () -> {
// Make sure to not always load 2 pages at once
viewBinding.recyclerView.post(() -> isLoadingMore = false);
});
}

@Subscribe(threadMode = ThreadMode.MAIN)
Expand Down Expand Up @@ -608,45 +651,4 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMen
MenuItemUtils.setOnClickListeners(menu, FeedItemlistFragment.this::onContextItemSelected);
}
}

public static class SingleFeedSortDialog extends ItemSortDialog {
private static final String ARG_FEED_ID = "feedId";
private static final String ARG_FEED_IS_LOCAL = "isLocal";
private static final String ARG_SORT_ORDER = "sortOrder";

private static SingleFeedSortDialog newInstance(Feed feed) {
Bundle bundle = new Bundle();
bundle.putLong(ARG_FEED_ID, feed.getId());
bundle.putBoolean(ARG_FEED_IS_LOCAL, feed.isLocalFeed());
if (feed.getSortOrder() == null) {
bundle.putString(ARG_SORT_ORDER, String.valueOf(SortOrder.DATE_NEW_OLD.code));
} else {
bundle.putString(ARG_SORT_ORDER, String.valueOf(feed.getSortOrder().code));
}
SingleFeedSortDialog dialog = new SingleFeedSortDialog();
dialog.setArguments(bundle);
return dialog;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sortOrder = SortOrder.fromCodeString(getArguments().getString(ARG_SORT_ORDER));
}

@Override
protected void onAddItem(int title, SortOrder ascending, SortOrder descending, boolean ascendingIsDefault) {
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG
|| ascending == SortOrder.EPISODE_TITLE_A_Z
|| (getArguments().getBoolean(ARG_FEED_IS_LOCAL) && ascending == SortOrder.EPISODE_FILENAME_A_Z)) {
super.onAddItem(title, ascending, descending, ascendingIsDefault);
}
}

@Override
protected void onSelectionChanged() {
super.onSelectionChanged();
DBWriter.setFeedItemSortOrder(getArguments().getLong(ARG_FEED_ID), sortOrder);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package de.danoeh.antennapod.ui.screen.feed;

import android.os.Bundle;
import androidx.annotation.Nullable;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.storage.database.DBWriter;

public class SingleFeedSortDialog extends ItemSortDialog {
private static final String ARG_FEED_ID = "feedId";
private static final String ARG_FEED_IS_LOCAL = "isLocal";
private static final String ARG_SORT_ORDER = "sortOrder";

public static SingleFeedSortDialog newInstance(Feed feed) {
Bundle bundle = new Bundle();
bundle.putLong(ARG_FEED_ID, feed.getId());
bundle.putBoolean(ARG_FEED_IS_LOCAL, feed.isLocalFeed());
if (feed.getSortOrder() == null) {
bundle.putString(ARG_SORT_ORDER, String.valueOf(SortOrder.DATE_NEW_OLD.code));
} else {
bundle.putString(ARG_SORT_ORDER, String.valueOf(feed.getSortOrder().code));
}
SingleFeedSortDialog dialog = new SingleFeedSortDialog();
dialog.setArguments(bundle);
return dialog;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sortOrder = SortOrder.fromCodeString(getArguments().getString(ARG_SORT_ORDER));
}

@Override
protected void onAddItem(int title, SortOrder ascending, SortOrder descending, boolean ascendingIsDefault) {
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG
|| ascending == SortOrder.EPISODE_TITLE_A_Z
|| (getArguments().getBoolean(ARG_FEED_IS_LOCAL) && ascending == SortOrder.EPISODE_FILENAME_A_Z)) {
super.onAddItem(title, ascending, descending, ascendingIsDefault);
}
}

@Override
protected void onSelectionChanged() {
super.onSelectionChanged();
DBWriter.setFeedItemSortOrder(getArguments().getLong(ARG_FEED_ID), sortOrder);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
.commitAllowingStateLoss();

disposable = Maybe.create((MaybeOnSubscribe<Feed>) emitter -> {
Feed feed = DBReader.getFeed(feedId);
Feed feed = DBReader.getFeed(feedId, false, 0, 0);
if (feed != null) {
emitter.onSuccess(feed);
} else {
Expand Down Expand Up @@ -163,7 +163,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {

long feedId = getArguments().getLong(EXTRA_FEED_ID);
disposable = Maybe.create((MaybeOnSubscribe<Feed>) emitter -> {
Feed feed = DBReader.getFeed(feedId);
Feed feed = DBReader.getFeed(feedId, false, 0, 0);
if (feed != null) {
emitter.onSuccess(feed);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ private void handleUpdatedFeedStatus() {
if (didPressSubscribe) {
didPressSubscribe = false;

Feed feed1 = DBReader.getFeed(getFeedId());
Feed feed1 = DBReader.getFeed(getFeedId(), false, 0, 0);
FeedPreferences feedPreferences = feed1.getPreferences();
if (UserPreferences.isEnableAutodownload()) {
boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public Result doWork() {
}
Collections.shuffle(toUpdate); // If the worker gets cancelled early, every feed has a chance to be updated
} else {
Feed feed = DBReader.getFeed(feedId);
Feed feed = DBReader.getFeed(feedId, false, 0, Integer.MAX_VALUE);
if (feed == null) {
return Result.success();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public void testGetFeedItemList() {
List<FeedItem> items = feed.getItems();
feed.setItems(null);
List<FeedItem> savedItems = DBReader.getFeedItemList(feed,
FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD);
FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD, 0, Integer.MAX_VALUE);
assertNotNull(savedItems);
assertEquals(items.size(), savedItems.size());
for (int i = 0; i < savedItems.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public void testUpdateFeedUpdatedFeed() {

updatedFeedTest(newFeed, feedID, itemIDs, numItemsOld, numItemsNew);

final Feed feedFromDB = DBReader.getFeed(newFeed.getId());
final Feed feedFromDB = DBReader.getFeed(newFeed.getId(), false, 0, Integer.MAX_VALUE);
assertNotNull(feedFromDB);
assertEquals(newFeed.getId(), feedFromDB.getId());
updatedFeedTest(feedFromDB, feedID, itemIDs, numItemsOld, numItemsNew);
Expand Down Expand Up @@ -163,7 +163,7 @@ public void testUpdateFeedMediaUrlResetState() {
final Feed newFeed = FeedDatabaseWriter.updateFeed(context, feed, false);
assertNotSame(newFeed, feed);

final Feed feedFromDB = DBReader.getFeed(newFeed.getId());
final Feed feedFromDB = DBReader.getFeed(newFeed.getId(), false, 0, Integer.MAX_VALUE);
final FeedItem feedItemFromDB = feedFromDB.getItems().get(0);
assertTrue(feedItemFromDB.isNew());
}
Expand All @@ -186,7 +186,7 @@ public void testUpdateFeedRemoveUnlistedItems() {
Feed newFeed = FeedDatabaseWriter.updateFeed(context, feed, true);
assertEquals(8, newFeed.getItems().size()); // 10 - 2 = 8 items

Feed feedFromDB = DBReader.getFeed(newFeed.getId());
Feed feedFromDB = DBReader.getFeed(newFeed.getId(), false, 0, Integer.MAX_VALUE);
assertEquals(8, feedFromDB.getItems().size()); // 10 - 2 = 8 items
}

Expand All @@ -213,7 +213,7 @@ public void testUpdateFeedSetDuplicate() {
Feed newFeed = FeedDatabaseWriter.updateFeed(context, feed, false);
assertEquals(10, newFeed.getItems().size()); // id 1-duplicate replaces because the stream url is the same

Feed feedFromDB = DBReader.getFeed(newFeed.getId());
Feed feedFromDB = DBReader.getFeed(newFeed.getId(), false, 0, Integer.MAX_VALUE);
assertEquals(10, feedFromDB.getItems().size()); // id1-duplicate should override id 1

FeedItem updatedItem = feedFromDB.getItemAtIndex(9);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ public void testRemoveAllNewFlags() throws Exception {

DBWriter.removeAllNewFlags().get();
List<FeedItem> loadedItems = DBReader.getFeedItemList(feed,
FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD);
FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD, 0, Integer.MAX_VALUE);
for (FeedItem item : loadedItems) {
assertFalse(item.isNew());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ public void testUpdateFeed_FeedMetadata() {
callUpdateFeed(LOCAL_FEED_DIR1);

Feed feed = verifySingleFeedInDatabase();
List<FeedItem> feedItems = DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD);
List<FeedItem> feedItems = DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(),
SortOrder.DATE_NEW_OLD, 0, Integer.MAX_VALUE);
assertEquals("track1.mp3", feedItems.get(0).getTitle());
}

Expand Down Expand Up @@ -283,7 +284,8 @@ private static Feed verifySingleFeedInDatabase() {
*/
private static void verifySingleFeedInDatabaseAndItemCount(int expectedItemCount) {
Feed feed = verifySingleFeedInDatabase();
List<FeedItem> feedItems = DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD);
List<FeedItem> feedItems = DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(),
SortOrder.DATE_NEW_OLD, 0, Integer.MAX_VALUE);
assertEquals(expectedItemCount, feedItems.size());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.model.playback.MediaType;
import de.danoeh.antennapod.model.playback.Playable;
import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer;
Expand Down Expand Up @@ -452,12 +451,7 @@ private List<MediaBrowserCompat.MediaItem> loadChildrenSynchronous(@NonNull Stri
new FeedItemFilter(FeedItemFilter.UNPLAYED), UserPreferences.getAllEpisodesSortOrder());
} else if (parentId.startsWith("FeedId:")) {
long feedId = Long.parseLong(parentId.split(":")[1]);
Feed feed = DBReader.getFeed(feedId);
SortOrder sortOrder = feed.getSortOrder();
if (sortOrder == null) {
sortOrder = SortOrder.DATE_NEW_OLD;
}
feedItems = DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), sortOrder);
feedItems = DBReader.getFeed(feedId, true, 0, MAX_ANDROID_AUTO_EPISODES_PER_FEED).getItems();
} else if (parentId.equals(getString(R.string.current_playing_episode))) {
FeedMedia playable = DBReader.getFeedMedia(PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
if (playable != null) {
Expand Down
Loading

0 comments on commit f3bca9d

Please sign in to comment.