Merge pull request #4563 from SebiderSushi/fix_episodes_list_item_loading_b

Keep loading on the All Episodes tab even if items are filtered out
This commit is contained in:
ByteHamster 2021-01-22 15:46:21 +01:00 committed by GitHub
commit 7bd20ae406
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 151 additions and 85 deletions

View File

@ -11,15 +11,11 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.test.antennapod.EspressoTestUtils;
import de.test.antennapod.ui.UITestUtils;
@ -70,7 +66,6 @@ public class ShareDialogTest {
onView(withText(R.string.all_episodes_short_label)).perform(click());
Matcher<View> allEpisodesMatcher;
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
allEpisodesMatcher = Matchers.allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2));
onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000));
onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, click()));

View File

@ -10,6 +10,7 @@ import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import de.danoeh.antennapod.core.feed.FeedItemFilter;
import org.awaitility.Awaitility;
import org.hamcrest.Matcher;
import org.junit.After;
@ -252,7 +253,7 @@ public class PlaybackTest {
onView(isRoot()).perform(waitForView(withText(R.string.all_episodes_short_label), 1000));
onView(withText(R.string.all_episodes_short_label)).perform(click());
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered());
Matcher<View> allEpisodesMatcher = allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2));
onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000));
onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, clickChildViewWithId(R.id.secondaryActionButton)));
@ -287,7 +288,7 @@ public class PlaybackTest {
uiTestUtils.addLocalFeedData(true);
DBWriter.clearQueue().get();
activityTestRule.launchActivity(new Intent());
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered());
startLocalPlayback();
FeedMedia media = episodes.get(0).getMedia();

View File

@ -104,13 +104,12 @@ public class AllEpisodesFragment extends EpisodesListFragment {
@NonNull
@Override
protected List<FeedItem> loadData() {
return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE));
return DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE, feedItemFilter);
}
@NonNull
@Override
protected List<FeedItem> loadMoreData() {
return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE,
EPISODES_PER_PAGE));
return DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, feedItemFilter);
}
}

View File

@ -383,6 +383,14 @@ public abstract class EpisodesListFragment extends Fragment {
@NonNull
protected abstract List<FeedItem> loadData();
/**
* Load a new page of data as defined by {@link #page} and {@link #EPISODES_PER_PAGE}.
* If the number of items returned is less than {@link #EPISODES_PER_PAGE},
* it will be assumed that the underlying data is exhausted
* and this method will not be called again.
*
* @return The items from the next page of data
*/
@NonNull
protected abstract List<FeedItem> loadMoreData();
}

View File

@ -3,6 +3,7 @@ package de.danoeh.antennapod.core.feed;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.danoeh.antennapod.core.storage.DBReader;
@ -12,77 +13,58 @@ import static de.danoeh.antennapod.core.feed.FeedItem.TAG_FAVORITE;
public class FeedItemFilter {
private final String[] mProperties;
private final String[] properties;
private boolean showPlayed = false;
private boolean showUnplayed = false;
private boolean showPaused = false;
private boolean showNotPaused = false;
private boolean showQueued = false;
private boolean showNotQueued = false;
private boolean showDownloaded = false;
private boolean showNotDownloaded = false;
private boolean showHasMedia = false;
private boolean showNoMedia = false;
private boolean showIsFavorite = false;
private boolean showNotFavorite = false;
public final boolean showPlayed;
public final boolean showUnplayed;
public final boolean showPaused;
public final boolean showNotPaused;
public final boolean showQueued;
public final boolean showNotQueued;
public final boolean showDownloaded;
public final boolean showNotDownloaded;
public final boolean showHasMedia;
public final boolean showNoMedia;
public final boolean showIsFavorite;
public final boolean showNotFavorite;
public static FeedItemFilter unfiltered() {
return new FeedItemFilter("");
}
public FeedItemFilter(String properties) {
this(TextUtils.split(properties, ","));
}
public FeedItemFilter(String[] properties) {
this.mProperties = properties;
for (String property : properties) {
// see R.arrays.feed_filter_values
switch (property) {
case "unplayed":
showUnplayed = true;
break;
case "paused":
showPaused = true;
break;
case "not_paused":
showNotPaused = true;
break;
case "played":
showPlayed = true;
break;
case "queued":
showQueued = true;
break;
case "not_queued":
showNotQueued = true;
break;
case "downloaded":
showDownloaded = true;
break;
case "not_downloaded":
showNotDownloaded = true;
break;
case "has_media":
showHasMedia = true;
break;
case "no_media":
showNoMedia = true;
break;
case "is_favorite":
showIsFavorite = true;
break;
case "not_favorite":
showNotFavorite = true;
break;
default:
break;
}
}
this.properties = properties;
// see R.arrays.feed_filter_values
showUnplayed = hasProperty("unplayed");
showPaused = hasProperty("paused");
showNotPaused = hasProperty("not_paused");
showPlayed = hasProperty("played");
showQueued = hasProperty("queued");
showNotQueued = hasProperty("not_queued");
showDownloaded = hasProperty("downloaded");
showNotDownloaded = hasProperty("not_downloaded");
showHasMedia = hasProperty("has_media");
showNoMedia = hasProperty("no_media");
showIsFavorite = hasProperty("is_favorite");
showNotFavorite = hasProperty("not_favorite");
}
private boolean hasProperty(String property) {
return Arrays.asList(properties).contains(property);
}
/**
* Run a list of feed items through the filter.
*/
public List<FeedItem> filter(List<FeedItem> items) {
if(mProperties.length == 0) return items;
if (properties.length == 0) {
return items;
}
List<FeedItem> result = new ArrayList<>();
@ -124,11 +106,10 @@ public class FeedItemFilter {
}
public String[] getValues() {
return mProperties.clone();
return properties.clone();
}
public boolean isShowDownloaded() {
return showDownloaded;
}
}

View File

@ -17,6 +17,7 @@ import java.util.Map;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedItemFilter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.feed.SubscriptionsFilter;
@ -366,18 +367,19 @@ public final class DBReader {
}
/**
* Loads a list of FeedItems sorted by pubDate in descending order.
* Loads a filtered list of FeedItems sorted by pubDate in descending order.
*
* @param offset The first episode that should be loaded.
* @param limit The maximum number of episodes that should be loaded.
* @param filter The filter describing which episodes to filter out.
*/
@NonNull
public static List<FeedItem> getRecentlyPublishedEpisodes(int offset, int limit) {
Log.d(TAG, "getRecentlyPublishedEpisodes() called with: " + "offset = [" + offset + "]" + " limit = [" + limit + "]" );
public static List<FeedItem> getRecentlyPublishedEpisodes(int offset, int limit, FeedItemFilter filter) {
Log.d(TAG, "getRecentlyPublishedEpisodes() called with: offset=" + offset + ", limit=" + limit);
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
try (Cursor cursor = adapter.getRecentlyPublishedItemsCursor(offset, limit)) {
try (Cursor cursor = adapter.getRecentlyPublishedItemsCursor(offset, limit, filter)) {
List<FeedItem> items = extractItemlistFromCursor(adapter, cursor);
loadAdditionalFeedItemListData(items);
return items;

View File

@ -16,6 +16,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.danoeh.antennapod.core.storage.mapper.FeedItemFilterQuery;
import org.apache.commons.io.FileUtils;
import java.io.File;
@ -30,6 +31,7 @@ import java.util.Set;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedItemFilter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@ -115,14 +117,14 @@ public class PodDBAdapter {
public static final String KEY_FEED_SKIP_ENDING = "feed_skip_ending";
// Table names
static final String TABLE_NAME_FEEDS = "Feeds";
static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
static final String TABLE_NAME_QUEUE = "Queue";
static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
static final String TABLE_NAME_FAVORITES = "Favorites";
public static final String TABLE_NAME_FEEDS = "Feeds";
public static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
public static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
public static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
public static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
public static final String TABLE_NAME_QUEUE = "Queue";
public static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
public static final String TABLE_NAME_FAVORITES = "Favorites";
// SQL Statements for creating new tables
private static final String TABLE_PRIMARY_KEY = KEY_ID
@ -1044,9 +1046,11 @@ public class PodDBAdapter {
return db.rawQuery(query, null);
}
public final Cursor getRecentlyPublishedItemsCursor(int offset, int limit) {
final String query = SELECT_FEED_ITEMS_AND_MEDIA
+ "ORDER BY " + KEY_PUBDATE + " DESC LIMIT " + offset + ", " + limit;
public final Cursor getRecentlyPublishedItemsCursor(int offset, int limit, FeedItemFilter filter) {
String filterQuery = FeedItemFilterQuery.generateFrom(filter);
String whereClause = "".equals(filterQuery) ? "" : " WHERE " + filterQuery;
final String query = SELECT_FEED_ITEMS_AND_MEDIA + whereClause
+ " ORDER BY " + KEY_PUBDATE + " DESC LIMIT " + offset + ", " + limit;
return db.rawQuery(query, null);
}

View File

@ -0,0 +1,76 @@
package de.danoeh.antennapod.core.storage.mapper;
import de.danoeh.antennapod.core.feed.FeedItemFilter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import java.util.ArrayList;
import java.util.List;
public class FeedItemFilterQuery {
private FeedItemFilterQuery() {
// Must not be instantiated
}
/**
* Express the filter using an SQL boolean statement that can be inserted into an SQL WHERE clause
* to yield output filtered according to the rules of this filter.
*
* @return An SQL boolean statement that matches the desired items,
* empty string if there is nothing to filter
*/
public static String generateFrom(FeedItemFilter filter) {
// The keys used within this method, but explicitly combined with their table
String keyRead = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_READ;
String keyPosition = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_POSITION;
String keyDownloaded = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DOWNLOADED;
String keyMediaId = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_ID;
String keyItemId = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID;
String keyFeedItem = PodDBAdapter.KEY_FEEDITEM;
String tableQueue = PodDBAdapter.TABLE_NAME_QUEUE;
String tableFavorites = PodDBAdapter.TABLE_NAME_FAVORITES;
List<String> statements = new ArrayList<>();
if (filter.showPlayed) {
statements.add(keyRead + " = 1 ");
} else if (filter.showUnplayed) {
statements.add(" NOT " + keyRead + " = 1 "); // Match "New" items (read = -1) as well
}
if (filter.showPaused) {
statements.add(" (" + keyPosition + " NOT NULL AND " + keyPosition + " > 0 " + ") ");
} else if (filter.showNotPaused) {
statements.add(" (" + keyPosition + " IS NULL OR " + keyPosition + " = 0 " + ") ");
}
if (filter.showQueued) {
statements.add(keyItemId + " IN (SELECT " + keyFeedItem + " FROM " + tableQueue + ") ");
} else if (filter.showNotQueued) {
statements.add(keyItemId + " NOT IN (SELECT " + keyFeedItem + " FROM " + tableQueue + ") ");
}
if (filter.showDownloaded) {
statements.add(keyDownloaded + " = 1 ");
} else if (filter.showNotDownloaded) {
statements.add(keyDownloaded + " = 0 ");
}
if (filter.showHasMedia) {
statements.add(keyMediaId + " NOT NULL ");
} else if (filter.showNoMedia) {
statements.add(keyMediaId + " IS NULL ");
}
if (filter.showIsFavorite) {
statements.add(keyItemId + " IN (SELECT " + keyFeedItem + " FROM " + tableFavorites + ") ");
} else if (filter.showNotFavorite) {
statements.add(keyItemId + " NOT IN (SELECT " + keyFeedItem + " FROM " + tableFavorites + ") ");
}
if (statements.isEmpty()) {
return "";
}
StringBuilder query = new StringBuilder(" (" + statements.get(0));
for (String r : statements.subList(1, statements.size())) {
query.append(" AND ");
query.append(r);
}
query.append(") ");
return query.toString();
}
}