diff options
10 files changed, 155 insertions, 99 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java index 647ab911f..b50c4dca6 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java @@ -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) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java index fdb74fa64..e4276b3b9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -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)); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index 5dbb703b7..b6d6dd9bb 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -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(); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java index f73735658..87a555cfd 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java @@ -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); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java index 2bfdd040b..bd3fd06b0 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -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); } } diff --git a/app/src/main/res/layout/all_episodes_fragment.xml b/app/src/main/res/layout/all_episodes_fragment.xml index 9160998ac..784e7a1c8 100644 --- a/app/src/main/res/layout/all_episodes_fragment.xml +++ b/app/src/main/res/layout/all_episodes_fragment.xml @@ -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>
\ No newline at end of file diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java index d029e7bfb..6e4054009 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java @@ -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) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index 8b87d7c54..9fe9eae1f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -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)); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index 96c3a46da..ab584df56 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -987,14 +987,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); } @@ -1027,16 +1030,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); } diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index ce6a0e41d..e59eac331 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -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> |