Merge pull request #3711 from ByteHamster/load-more-new-episodes

Added paged loading to 'new episodes' list
This commit is contained in:
H. Lehmann 2020-01-09 20:59:19 +01:00 committed by GitHub
commit b3f76e333b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 155 additions and 99 deletions

View File

@ -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) {

View File

@ -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));
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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) {

View File

@ -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));

View File

@ -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);
}

View File

@ -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>