Merge pull request #3711 from ByteHamster/load-more-new-episodes
Added paged loading to 'new episodes' list
This commit is contained in:
commit
b3f76e333b
@ -267,11 +267,11 @@ public class DBReaderTest {
|
||||
for (int i = 0; i < newItems.size(); i++) {
|
||||
unreadIds[i] = newItems.get(i).getId();
|
||||
}
|
||||
List<FeedItem> newItemsSaved = DBReader.getNewItemsList();
|
||||
List<FeedItem> newItemsSaved = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
|
||||
assertNotNull(newItemsSaved);
|
||||
assertTrue(newItems.size() == newItemsSaved.size());
|
||||
for(int i=0; i < newItemsSaved.size(); i++) {
|
||||
long savedId = newItemsSaved.get(i).getId();
|
||||
for (FeedItem feedItem : newItemsSaved) {
|
||||
long savedId = feedItem.getId();
|
||||
boolean found = false;
|
||||
for (long id : unreadIds) {
|
||||
if (id == savedId) {
|
||||
|
@ -3,26 +3,18 @@ package de.danoeh.antennapod.fragment;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedItemFilter;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.dialog.FilterDialog;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
@ -33,16 +25,18 @@ import java.util.Set;
|
||||
* supports swiping to mark as read.
|
||||
*/
|
||||
public class AllEpisodesFragment extends EpisodesListFragment {
|
||||
|
||||
public static final String TAG = "AllEpisodesFragment";
|
||||
private static final String PREF_NAME = "PrefAllEpisodesFragment";
|
||||
private static final String PREF_FILTER = "filter";
|
||||
|
||||
private static final int EPISODES_PER_PAGE = 150;
|
||||
private static final int VISIBLE_EPISODES_SCROLL_THRESHOLD = 5;
|
||||
private static int page = 1;
|
||||
private static FeedItemFilter feedItemFilter = new FeedItemFilter("");
|
||||
|
||||
private FeedItemFilter feedItemFilter = new FeedItemFilter("");
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
feedItemFilter = new FeedItemFilter(prefs.getString(PREF_FILTER, ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean showOnlyNewEpisodes() {
|
||||
@ -69,53 +63,6 @@ public class AllEpisodesFragment extends EpisodesListFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View root = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
feedItemFilter = new FeedItemFilter(prefs.getString(PREF_FILTER, ""));
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
|
||||
/* Total number of episodes after last load */
|
||||
private int previousTotalEpisodes = 0;
|
||||
|
||||
/* True if loading more episodes is still in progress */
|
||||
private boolean isLoadingMore = true;
|
||||
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int deltaX, int deltaY) {
|
||||
super.onScrolled(recyclerView, deltaX, deltaY);
|
||||
|
||||
int visibleEpisodeCount = recyclerView.getChildCount();
|
||||
int totalEpisodeCount = recyclerView.getLayoutManager().getItemCount();
|
||||
int firstVisibleEpisode = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
|
||||
|
||||
/* Determine if loading more episodes has finished */
|
||||
if (isLoadingMore) {
|
||||
if (totalEpisodeCount > previousTotalEpisodes) {
|
||||
isLoadingMore = false;
|
||||
previousTotalEpisodes = totalEpisodeCount;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine if the user scrolled to the bottom and loading more episodes is not already in progress */
|
||||
if (!isLoadingMore && (totalEpisodeCount - visibleEpisodeCount)
|
||||
<= (firstVisibleEpisode + VISIBLE_EPISODES_SCROLL_THRESHOLD)) {
|
||||
|
||||
/* The end of the list has been reached. Load more data. */
|
||||
page++;
|
||||
loadMoreItems();
|
||||
isLoadingMore = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
@ -136,20 +83,6 @@ public class AllEpisodesFragment extends EpisodesListFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadMoreItems() {
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
disposable = Observable.fromCallable(this::loadMoreData)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(data -> {
|
||||
progLoading.setVisibility(View.GONE);
|
||||
episodes.addAll(data);
|
||||
onFragmentLoaded(episodes);
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
private void showFilterDialog() {
|
||||
FilterDialog filterDialog = new FilterDialog(getContext(), feedItemFilter) {
|
||||
@Override
|
||||
@ -167,10 +100,13 @@ public class AllEpisodesFragment extends EpisodesListFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<FeedItem> loadData() {
|
||||
return feedItemFilter.filter( DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE));
|
||||
return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE));
|
||||
}
|
||||
|
||||
List<FeedItem> loadMoreData() {
|
||||
return feedItemFilter.filter( DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE));
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<FeedItem> loadMoreData() {
|
||||
return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE,
|
||||
EPISODES_PER_PAGE));
|
||||
}
|
||||
}
|
||||
|
@ -70,9 +70,14 @@ public abstract class EpisodesListFragment extends Fragment {
|
||||
private static final String PREF_SCROLL_POSITION = "scroll_position";
|
||||
private static final String PREF_SCROLL_OFFSET = "scroll_offset";
|
||||
|
||||
protected static final int EPISODES_PER_PAGE = 150;
|
||||
private static final int VISIBLE_EPISODES_SCROLL_THRESHOLD = 5;
|
||||
protected int page = 1;
|
||||
|
||||
RecyclerView recyclerView;
|
||||
AllEpisodesRecycleAdapter listAdapter;
|
||||
ProgressBar progLoading;
|
||||
View loadingMore;
|
||||
EmptyViewHandler emptyView;
|
||||
|
||||
@NonNull
|
||||
@ -264,6 +269,7 @@ public abstract class EpisodesListFragment extends Fragment {
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
setupLoadMoreScrollListener();
|
||||
|
||||
RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator();
|
||||
if (animator instanceof SimpleItemAnimator) {
|
||||
@ -272,6 +278,7 @@ public abstract class EpisodesListFragment extends Fragment {
|
||||
|
||||
progLoading = root.findViewById(R.id.progLoading);
|
||||
progLoading.setVisibility(View.VISIBLE);
|
||||
loadingMore = root.findViewById(R.id.loadingMore);
|
||||
|
||||
emptyView = new EmptyViewHandler(getContext());
|
||||
emptyView.attachToRecyclerView(recyclerView);
|
||||
@ -285,6 +292,60 @@ public abstract class EpisodesListFragment extends Fragment {
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupLoadMoreScrollListener() {
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
|
||||
/* Total number of episodes after last load */
|
||||
private int previousTotalEpisodes = 0;
|
||||
|
||||
/* True if loading more episodes is still in progress */
|
||||
private boolean isLoadingMore = true;
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int deltaX, int deltaY) {
|
||||
super.onScrolled(recyclerView, deltaX, deltaY);
|
||||
|
||||
int visibleEpisodeCount = recyclerView.getChildCount();
|
||||
int totalEpisodeCount = recyclerView.getLayoutManager().getItemCount();
|
||||
int firstVisibleEpisode = layoutManager.findFirstVisibleItemPosition();
|
||||
|
||||
/* Determine if loading more episodes has finished */
|
||||
if (isLoadingMore) {
|
||||
if (totalEpisodeCount > previousTotalEpisodes) {
|
||||
isLoadingMore = false;
|
||||
previousTotalEpisodes = totalEpisodeCount;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine if the user scrolled to the bottom and loading more episodes is not already in progress */
|
||||
if (!isLoadingMore && (totalEpisodeCount - visibleEpisodeCount)
|
||||
<= (firstVisibleEpisode + VISIBLE_EPISODES_SCROLL_THRESHOLD)) {
|
||||
|
||||
/* The end of the list has been reached. Load more data. */
|
||||
page++;
|
||||
loadMoreItems();
|
||||
isLoadingMore = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadMoreItems() {
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
loadingMore.setVisibility(View.VISIBLE);
|
||||
disposable = Observable.fromCallable(this::loadMoreData)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(data -> {
|
||||
loadingMore.setVisibility(View.GONE);
|
||||
progLoading.setVisibility(View.GONE);
|
||||
episodes.addAll(data);
|
||||
onFragmentLoaded(episodes);
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
protected void onFragmentLoaded(List<FeedItem> episodes) {
|
||||
listAdapter.notifyDataSetChanged();
|
||||
|
||||
@ -453,4 +514,7 @@ public abstract class EpisodesListFragment extends Fragment {
|
||||
|
||||
@NonNull
|
||||
protected abstract List<FeedItem> loadData();
|
||||
|
||||
@NonNull
|
||||
protected abstract List<FeedItem> loadMoreData();
|
||||
}
|
||||
|
@ -88,6 +88,12 @@ public class FavoriteEpisodesFragment extends EpisodesListFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<FeedItem> loadData() {
|
||||
return DBReader.getFavoriteItemsList();
|
||||
return DBReader.getFavoriteItemsList(0, page * EPISODES_PER_PAGE);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<FeedItem> loadMoreData() {
|
||||
return DBReader.getFavoriteItemsList((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE);
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,12 @@ public class NewEpisodesFragment extends EpisodesListFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<FeedItem> loadData() {
|
||||
return DBReader.getNewItemsList();
|
||||
return DBReader.getNewItemsList(0, page * EPISODES_PER_PAGE);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<FeedItem> loadMoreData() {
|
||||
return DBReader.getNewItemsList((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="@dimen/list_vertical_padding"
|
||||
android:paddingBottom="@dimen/list_vertical_padding"
|
||||
android:layout_above="@id/loadingMore"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
@ -43,9 +44,36 @@
|
||||
android:indeterminateOnly="true"
|
||||
android:visibility="gone"
|
||||
android:layout_centerInParent="true"
|
||||
tools:visibility="gone"
|
||||
tools:layout_width="match_parent"
|
||||
tools:layout_height="64dp"
|
||||
tools:background="@android:color/holo_red_light"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loadingMore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:visibility="gone"
|
||||
android:gravity="center">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:indeterminateOnly="true"
|
||||
android:layout_centerInParent="true"
|
||||
tools:background="@android:color/holo_red_light" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/loading_more" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -50,7 +50,7 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
|
||||
|
||||
List<FeedItem> candidates;
|
||||
final List<FeedItem> queue = DBReader.getQueue();
|
||||
final List<FeedItem> newItems = DBReader.getNewItemsList();
|
||||
final List<FeedItem> newItems = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
|
||||
candidates = new ArrayList<>(queue.size() + newItems.size());
|
||||
candidates.addAll(queue);
|
||||
for (FeedItem newItem : newItems) {
|
||||
|
@ -353,16 +353,18 @@ public final class DBReader {
|
||||
* Loads a list of FeedItems that are considered new.
|
||||
* Excludes items from feeds that do not have keep updated enabled.
|
||||
*
|
||||
* @param offset The first episode that should be loaded.
|
||||
* @param limit The maximum number of episodes that should be loaded.
|
||||
* @return A list of FeedItems that are considered new.
|
||||
*/
|
||||
public static List<FeedItem> getNewItemsList() {
|
||||
public static List<FeedItem> getNewItemsList(int offset, int limit) {
|
||||
Log.d(TAG, "getNewItemsList() called");
|
||||
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = adapter.getNewItemsCursor();
|
||||
cursor = adapter.getNewItemsCursor(offset, limit);
|
||||
List<FeedItem> items = extractItemlistFromCursor(adapter, cursor);
|
||||
loadAdditionalFeedItemListData(items);
|
||||
return items;
|
||||
@ -374,14 +376,21 @@ public final class DBReader {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<FeedItem> getFavoriteItemsList() {
|
||||
/**
|
||||
* Loads a list of favorite items.
|
||||
*
|
||||
* @param offset The first episode that should be loaded.
|
||||
* @param limit The maximum number of episodes that should be loaded.
|
||||
* @return A list of FeedItems that are marked as favorite.
|
||||
*/
|
||||
public static List<FeedItem> getFavoriteItemsList(int offset, int limit) {
|
||||
Log.d(TAG, "getFavoriteItemsList() called");
|
||||
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = adapter.getFavoritesCursor();
|
||||
cursor = adapter.getFavoritesCursor(offset, limit);
|
||||
List<FeedItem> items = extractItemlistFromCursor(adapter, cursor);
|
||||
loadAdditionalFeedItemListData(items);
|
||||
return items;
|
||||
@ -400,7 +409,7 @@ public final class DBReader {
|
||||
adapter.open();
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = adapter.getFavoritesCursor();
|
||||
cursor = adapter.getFavoritesCursor(0, Integer.MAX_VALUE);
|
||||
LongList favoriteIDs = new LongList(cursor.getCount());
|
||||
while (cursor.moveToNext()) {
|
||||
favoriteIDs.add(cursor.getLong(0));
|
||||
|
@ -944,14 +944,17 @@ public class PodDBAdapter {
|
||||
}
|
||||
|
||||
|
||||
public final Cursor getFavoritesCursor() {
|
||||
public final Cursor getFavoritesCursor(int offset, int limit) {
|
||||
Object[] args = new String[]{
|
||||
SEL_FI_SMALL_STR,
|
||||
TABLE_NAME_FEED_ITEMS, TABLE_NAME_FAVORITES,
|
||||
TABLE_NAME_FEED_ITEMS + "." + KEY_ID,
|
||||
TABLE_NAME_FAVORITES + "." + KEY_FEEDITEM,
|
||||
TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE};
|
||||
String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s DESC", args);
|
||||
TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE,
|
||||
String.valueOf(offset),
|
||||
String.valueOf(limit)
|
||||
};
|
||||
String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s DESC LIMIT %s, %s", args);
|
||||
return db.rawQuery(query, null);
|
||||
}
|
||||
|
||||
@ -984,16 +987,19 @@ public class PodDBAdapter {
|
||||
* Excludes those feeds that do not have 'Keep Updated' enabled.
|
||||
* The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection.
|
||||
*/
|
||||
public final Cursor getNewItemsCursor() {
|
||||
public final Cursor getNewItemsCursor(int offset, int limit) {
|
||||
Object[] args = new String[]{
|
||||
SEL_FI_SMALL_STR,
|
||||
TABLE_NAME_FEED_ITEMS,
|
||||
TABLE_NAME_FEEDS,
|
||||
TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID,
|
||||
TABLE_NAME_FEED_ITEMS + "." + KEY_READ + "=" + FeedItem.NEW + " AND " + TABLE_NAME_FEEDS + "." + KEY_KEEP_UPDATED + " > 0",
|
||||
KEY_PUBDATE + " DESC"
|
||||
KEY_PUBDATE + " DESC",
|
||||
String.valueOf(offset),
|
||||
String.valueOf(limit)
|
||||
};
|
||||
final String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s WHERE %s ORDER BY %s", args);
|
||||
final String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s WHERE %s "
|
||||
+ "ORDER BY %s LIMIT %s, %s", args);
|
||||
return db.rawQuery(query, null);
|
||||
}
|
||||
|
||||
|
@ -108,6 +108,7 @@
|
||||
<item quantity="other">%d days after finishing</item>
|
||||
</plurals>
|
||||
<string name="num_selected_label">%d selected</string>
|
||||
<string name="loading_more">Loading more…</string>
|
||||
|
||||
<!-- 'Add Feed' Activity labels -->
|
||||
<string name="feedurl_label">Feed URL</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user