Merge pull request #3528 from orionlee/podcast_screen_sort_2524

Sort Support in Podcast screen
This commit is contained in:
H. Lehmann 2019-10-25 11:38:31 +02:00 committed by GitHub
commit a047e34f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 404 additions and 112 deletions

View File

@ -44,8 +44,8 @@ class DBTestUtils {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
for (int i = 0; i < numFeeds; i++) {
Feed f = new Feed(0, null, "feed " + i, null, "link" + i, "descr", null, null,
null, null, "id" + i, null, null, "url" + i, false, false, null, null, false);
Feed f = new Feed(0, null, "feed " + i, "link" + i, "descr", null, null,
null, null, "id" + i, null, null, "url" + i, false);
f.setItems(new ArrayList<>());
for (int j = 0; j < numItems; j++) {
FeedItem item = new FeedItem(0, "item " + j, "id" + j, "link" + j, new Date(),

View File

@ -0,0 +1,51 @@
package de.danoeh.antennapod.dialog;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.util.SortOrder;
public abstract class IntraFeedSortDialog {
@Nullable
protected SortOrder currentSortOrder;
@NonNull
protected Context context;
public IntraFeedSortDialog(@NonNull Context context, @Nullable SortOrder sortOrder) {
this.context = context;
this.currentSortOrder = sortOrder;
}
public void openDialog() {
final String[] items = context.getResources().getStringArray(R.array.feed_episodes_sort_options);
final String[] valueStrs = context.getResources().getStringArray(R.array.feed_episodes_sort_values);
final SortOrder[] values = new SortOrder[valueStrs.length];
for (int i = 0; i < valueStrs.length; i++) {
values[i] = SortOrder.valueOf(valueStrs[i]);
}
int idxCurrentSort = -1;
for (int i = 0; i < values.length; i++) {
if (currentSortOrder == values[i]) {
idxCurrentSort = i;
break;
}
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.sort);
builder.setSingleChoiceItems(items, idxCurrentSort, (dialog, idxNewSort) -> {
updateSort(values[idxNewSort]);
dialog.dismiss();
});
builder.setNegativeButton(R.string.cancel_label, null);
builder.create().show();
}
protected abstract void updateSort(@NonNull SortOrder sortOrder);
}

View File

@ -5,10 +5,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.graphics.LightingColorFilter;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.ListFragment;
import androidx.core.view.MenuItemCompat;
import androidx.appcompat.widget.SearchView;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
@ -23,12 +19,16 @@ import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.ListFragment;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.joanzapata.iconify.Iconify;
import com.joanzapata.iconify.widget.IconTextView;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import org.apache.commons.lang3.Validate;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
@ -45,6 +45,7 @@ import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.DownloaderUpdate;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedEvent;
@ -59,6 +60,7 @@ import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.Optional;
@ -192,6 +194,7 @@ public class FeedItemlistFragment extends ListFragment {
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
menu.findItem(R.id.sort_items).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
menu.findItem(R.id.filter_items).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
menu.findItem(R.id.episode_actions).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
menu.findItem(R.id.refresh_item).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
@ -629,6 +632,11 @@ public class FeedItemlistFragment extends ListFragment {
FeedItemFilter filter = feed.getItemFilter();
feed.setItems(filter.filter(feed.getItems()));
}
if (feed != null && feed.getSortOrder() != null) {
List<FeedItem> feedItems = feed.getItems();
FeedItemPermutors.getPermutor(feed.getSortOrder()).reorder(feedItems);
feed.setItems(feedItems);
}
return Optional.ofNullable(feed);
}

View File

@ -4,15 +4,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
import androidx.core.view.MenuItemCompat;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.recyclerview.widget.ItemTouchHelper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@ -24,9 +15,18 @@ import android.widget.CheckBox;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.google.android.material.snackbar.Snackbar;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@ -40,6 +40,7 @@ import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.DownloaderUpdate;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.FeedItem;
@ -54,7 +55,6 @@ import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.QueueSorter;
import de.danoeh.antennapod.core.util.SortOrder;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
@ -372,7 +372,7 @@ public class QueueFragment extends Fragment {
UserPreferences.setQueueKeepSorted(keepSortedNew);
if (keepSortedNew) {
SortOrder sortOrder = UserPreferences.getQueueKeepSortedOrder();
QueueSorter.sort(sortOrder, true);
DBWriter.reorderQueue(sortOrder, true);
if (recyclerAdapter != null) {
recyclerAdapter.setLocked(true);
}
@ -438,7 +438,7 @@ public class QueueFragment extends Fragment {
*/
private void setSortOrder(SortOrder sortOrder) {
UserPreferences.setQueueKeepSortedOrder(sortOrder);
QueueSorter.sort(sortOrder, true);
DBWriter.reorderQueue(sortOrder, true);
}
@Override

View File

@ -7,6 +7,8 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import org.apache.commons.lang3.StringUtils;
import java.util.Set;
@ -19,7 +21,9 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.ShareUtils;
import de.danoeh.antennapod.core.util.SortOrder;
import de.danoeh.antennapod.dialog.FilterDialog;
import de.danoeh.antennapod.dialog.IntraFeedSortDialog;
/**
* Handles interactions with the FeedItemMenu.
@ -65,6 +69,9 @@ public class FeedMenuHandler {
case R.id.refresh_complete_item:
DBTasks.forceRefreshCompleteFeed(context, selectedFeed);
break;
case R.id.sort_items:
showSortDialog(context, selectedFeed);
break;
case R.id.filter_items:
showFilterDialog(context, selectedFeed);
break;
@ -108,4 +115,17 @@ public class FeedMenuHandler {
filterDialog.openDialog();
}
private static void showSortDialog(Context context, Feed selectedFeed) {
IntraFeedSortDialog sortDialog = new IntraFeedSortDialog(context, selectedFeed.getSortOrder()) {
@Override
protected void updateSort(@NonNull SortOrder sortOrder) {
selectedFeed.setSortOrder(sortOrder);
DBWriter.setFeedItemSortOrder(selectedFeed.getId(), sortOrder);
}
};
sortDialog.openDialog();
}
}

View File

@ -2,6 +2,13 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/sort_items"
android:icon="?attr/ic_sort"
android:menuCategory="container"
android:title="@string/sort"
custom:showAsAction="always">
</item>
<item
android:id="@+id/filter_items"
android:icon="?attr/ic_filter"
@ -9,13 +16,6 @@
android:title="@string/filter"
custom:showAsAction="always">
</item>
<item
android:id="@+id/episode_actions"
android:menuCategory="container"
android:icon="?attr/checkbox_multiple"
android:title="@string/batch_edit"
custom:showAsAction="always">
</item>
<item
android:id="@+id/refresh_item"
android:icon="?attr/navigation_refresh"
@ -37,6 +37,13 @@
custom:actionViewClass="androidx.appcompat.widget.SearchView"
android:title="@string/search_label"/>
<item
android:id="@+id/episode_actions"
android:menuCategory="container"
android:icon="?attr/checkbox_multiple"
android:title="@string/batch_edit"
custom:showAsAction="collapseActionView">
</item>
<item
android:id="@+id/visit_website_item"
android:icon="?attr/location_web_site"

View File

@ -1,9 +1,10 @@
package de.danoeh.antennapod.core.feed;
import android.database.Cursor;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -11,6 +12,8 @@ import java.util.List;
import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.SortOrder;
/**
* Data Object for a whole feed
*
@ -88,13 +91,20 @@ public class Feed extends FeedFile implements ImageResource {
*/
private FeedItemFilter itemfilter;
/**
* User-preferred sortOrder for display.
* Only those of scope {@link SortOrder.Scope#INTRA_FEED} is allowed.
*/
@Nullable
private SortOrder sortOrder;
/**
* This constructor is used for restoring a feed from the database.
*/
public Feed(long id, String lastUpdate, String title, String customTitle, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, boolean downloaded, boolean paged, String nextPageLink,
String filter, boolean lastUpdateFailed) {
String filter, @Nullable SortOrder sortOrder, boolean lastUpdateFailed) {
super(fileUrl, downloadUrl, downloaded);
this.id = id;
this.feedTitle = title;
@ -116,6 +126,7 @@ public class Feed extends FeedFile implements ImageResource {
} else {
this.itemfilter = new FeedItemFilter(new String[0]);
}
setSortOrder(sortOrder);
this.lastUpdateFailed = lastUpdateFailed;
}
@ -126,7 +137,7 @@ public class Feed extends FeedFile implements ImageResource {
String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, boolean downloaded) {
this(id, lastUpdate, title, null, link, description, paymentLink, author, language, type, feedIdentifier, imageUrl,
fileUrl, downloadUrl, downloaded, false, null, null, false);
fileUrl, downloadUrl, downloaded, false, null, null, null, false);
}
/**
@ -181,6 +192,7 @@ public class Feed extends FeedFile implements ImageResource {
int indexIsPaged = cursor.getColumnIndex(PodDBAdapter.KEY_IS_PAGED);
int indexNextPageLink = cursor.getColumnIndex(PodDBAdapter.KEY_NEXT_PAGE_LINK);
int indexHide = cursor.getColumnIndex(PodDBAdapter.KEY_HIDE);
int indexSortOrder = cursor.getColumnIndex(PodDBAdapter.KEY_SORT_ORDER);
int indexLastUpdateFailed = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED);
int indexImageUrl = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE_URL);
@ -203,6 +215,7 @@ public class Feed extends FeedFile implements ImageResource {
cursor.getInt(indexIsPaged) > 0,
cursor.getString(indexNextPageLink),
cursor.getString(indexHide),
SortOrder.fromCodeString(cursor.getString(indexSortOrder)),
cursor.getInt(indexLastUpdateFailed) > 0
);
@ -523,6 +536,19 @@ public class Feed extends FeedFile implements ImageResource {
}
}
@Nullable
public SortOrder getSortOrder() {
return sortOrder;
}
public void setSortOrder(@Nullable SortOrder sortOrder) {
if (sortOrder != null && sortOrder.scope != SortOrder.Scope.INTRA_FEED) {
throw new IllegalArgumentException("The specified sortOrder " + sortOrder
+ " is invalid. Only those with INTRA_FEED scope are allowed.");
}
this.sortOrder = sortOrder;
}
public boolean hasLastUpdateFailed() {
return this.lastUpdateFailed;
}

View File

@ -6,7 +6,8 @@ import org.apache.commons.lang3.builder.ToStringStyle;
public class FeedEvent {
public enum Action {
FILTER_CHANGED
FILTER_CHANGED,
SORT_ORDER_CHANGED
}
private final Action action;

View File

@ -294,6 +294,11 @@ class DBUpgrader {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL);
}
if (oldVersion < 1070401) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_SORT_ORDER + " TEXT");
}
}
}

View File

@ -5,6 +5,7 @@ import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.danoeh.antennapod.core.event.DownloadLogEvent;
import de.danoeh.antennapod.core.event.PlaybackHistoryEvent;
@ -38,10 +39,10 @@ import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.Permutor;
import de.danoeh.antennapod.core.util.QueueSorter;
import de.danoeh.antennapod.core.util.SortOrder;
/**
@ -387,7 +388,7 @@ public class DBWriter {
// do not shuffle the list on every change
return;
}
Permutor<FeedItem> permutor = QueueSorter.getPermutor(sortOrder);
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(sortOrder);
permutor.reorder(queue);
// Replace ADDED events by a single SORTED event
@ -847,14 +848,18 @@ public class DBWriter {
}
/**
* Sort the FeedItems in the queue with the given Permutor.
* Sort the FeedItems in the queue with the given the named sort order.
*
* @param permutor Encapsulates whole-Queue reordering logic.
* @param broadcastUpdate <code>true</code> if this operation should trigger a
* QueueUpdateBroadcast. This option should be set to <code>false</code>
* if the caller wants to avoid unexpected updates of the GUI.
*/
public static Future<?> reorderQueue(final Permutor<FeedItem> permutor, final boolean broadcastUpdate) {
public static Future<?> reorderQueue(@Nullable SortOrder sortOrder, final boolean broadcastUpdate) {
if (sortOrder == null) {
Log.w(TAG, "reorderQueue() - sortOrder is null. Do nothing.");
return dbExec.submit(() -> { });
}
final Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(sortOrder);
return dbExec.submit(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@ -946,6 +951,19 @@ public class DBWriter {
});
}
/**
* Set item sort order of the feed
*
*/
public static Future<?> setFeedItemSortOrder(long feedId, @Nullable SortOrder sortOrder) {
return dbExec.submit(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedItemSortOrder(feedId, sortOrder);
adapter.close();
EventBus.getDefault().post(new FeedEvent(FeedEvent.Action.SORT_ORDER_CHANGED, feedId));
});
}
/**
* Reset the statistics in DB

View File

@ -13,11 +13,11 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import org.apache.commons.io.FileUtils;
import java.io.File;
@ -35,8 +35,10 @@ import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.SortOrder;
import static de.danoeh.antennapod.core.feed.FeedPreferences.SPEED_USE_GLOBAL;
import static de.danoeh.antennapod.core.util.SortOrder.toCodeString;
// TODO Remove media column from feeditem table
@ -101,6 +103,7 @@ public class PodDBAdapter {
public static final String KEY_IS_PAGED = "is_paged";
public static final String KEY_NEXT_PAGE_LINK = "next_page_link";
public static final String KEY_HIDE = "hide";
public static final String KEY_SORT_ORDER = "sort_order";
public static final String KEY_LAST_UPDATE_FAILED = "last_update_failed";
public static final String KEY_HAS_EMBEDDED_PICTURE = "has_embedded_picture";
public static final String KEY_LAST_PLAYED_TIME = "last_played_time";
@ -138,6 +141,7 @@ public class PodDBAdapter {
+ KEY_IS_PAGED + " INTEGER DEFAULT 0,"
+ KEY_NEXT_PAGE_LINK + " TEXT,"
+ KEY_HIDE + " TEXT,"
+ KEY_SORT_ORDER + " TEXT,"
+ KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0,"
+ KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0,"
+ KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL + ")";
@ -234,6 +238,7 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_USERNAME,
TABLE_NAME_FEEDS + "." + KEY_PASSWORD,
TABLE_NAME_FEEDS + "." + KEY_HIDE,
TABLE_NAME_FEEDS + "." + KEY_SORT_ORDER,
TABLE_NAME_FEEDS + "." + KEY_LAST_UPDATE_FAILED,
TABLE_NAME_FEEDS + "." + KEY_AUTO_DELETE_ACTION,
TABLE_NAME_FEEDS + "." + KEY_INCLUDE_FILTER,
@ -378,6 +383,7 @@ public class PodDBAdapter {
} else {
values.put(KEY_HIDE, "");
}
values.put(KEY_SORT_ORDER, toCodeString(feed.getSortOrder()));
values.put(KEY_LAST_UPDATE_FAILED, feed.hasLastUpdateFailed());
if (feed.getId() == 0) {
// Create new entry
@ -416,6 +422,12 @@ public class PodDBAdapter {
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)});
}
public void setFeedItemSortOrder(long feedId, @Nullable SortOrder sortOrder) {
ContentValues values = new ContentValues();
values.put(KEY_SORT_ORDER, toCodeString(sortOrder));
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)});
}
/**
* Inserts or updates a media entry
*
@ -1455,7 +1467,7 @@ public class PodDBAdapter {
*/
private static class PodDBHelper extends SQLiteOpenHelper {
private static final int VERSION = 1070400;
private static final int VERSION = 1070401;
private final Context context;

View File

@ -1,89 +1,58 @@
package de.danoeh.antennapod.core.util;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.storage.DBWriter;
/**
* Provides method for sorting the queue according to rules.
* Provides method for sorting the a list of {@link FeedItem} according to rules.
*/
public class QueueSorter {
/**
* Sorts the queue by the given sort order and sends a broadcast update.
*
* @param sortOrder Sort order.
* @param broadcastUpdate Send broadcast update?
*/
public static void sort(SortOrder sortOrder, boolean broadcastUpdate) {
Permutor<FeedItem> permutor = getPermutor(sortOrder);
if (permutor != null) {
DBWriter.reorderQueue(permutor, broadcastUpdate);
}
}
public class FeedItemPermutors {
/**
* Returns a Permutor that sorts a list appropriate to the given sort order.
*
* @param sortOrder Sort order.
* @return Permutor that sorts a list appropriate to the given sort order. <code>null</code> if the order is unknown or <code>null</code>.
* @return Permutor that sorts a list appropriate to the given sort order.
*/
public static Permutor<FeedItem> getPermutor(SortOrder sortOrder) {
if (sortOrder == null) {
return null;
}
@NonNull
public static Permutor<FeedItem> getPermutor(@NonNull SortOrder sortOrder) {
Comparator<FeedItem> comparator = null;
Permutor<FeedItem> permutor = null;
switch (sortOrder) {
case EPISODE_TITLE_A_Z:
comparator = (f1, f2) -> f1.getTitle().compareTo(f2.getTitle());
comparator = (f1, f2) -> itemTitle(f1).compareTo(itemTitle(f2));
break;
case EPISODE_TITLE_Z_A:
comparator = (f1, f2) -> f2.getTitle().compareTo(f1.getTitle());
comparator = (f1, f2) -> itemTitle(f2).compareTo(itemTitle(f1));
break;
case DATE_OLD_NEW:
comparator = (f1, f2) -> f1.getPubDate().compareTo(f2.getPubDate());
comparator = (f1, f2) -> pubDate(f1).compareTo(pubDate(f2));
break;
case DATE_NEW_OLD:
comparator = (f1, f2) -> f2.getPubDate().compareTo(f1.getPubDate());
comparator = (f1, f2) -> pubDate(f2).compareTo(pubDate(f1));
break;
case DURATION_SHORT_LONG:
comparator = (f1, f2) -> {
FeedMedia f1Media = f1.getMedia();
FeedMedia f2Media = f2.getMedia();
int duration1 = f1Media != null ? f1Media.getDuration() : -1;
int duration2 = f2Media != null ? f2Media.getDuration() : -1;
if (duration1 == -1 || duration2 == -1)
return duration2 - duration1;
else
return duration1 - duration2;
};
comparator = (f1, f2) -> Integer.compare(duration(f1), duration(f2));
break;
case DURATION_LONG_SHORT:
comparator = (f1, f2) -> {
FeedMedia f1Media = f1.getMedia();
FeedMedia f2Media = f2.getMedia();
int duration1 = f1Media != null ? f1Media.getDuration() : -1;
int duration2 = f2Media != null ? f2Media.getDuration() : -1;
return -1 * (duration1 - duration2);
};
comparator = (f1, f2) -> Integer.compare(duration(f2), duration(f1));
break;
case FEED_TITLE_A_Z:
comparator = (f1, f2) -> f1.getFeed().getTitle().compareTo(f2.getFeed().getTitle());
comparator = (f1, f2) -> feedTitle(f1).compareTo(feedTitle(f2));
break;
case FEED_TITLE_Z_A:
comparator = (f1, f2) -> f2.getFeed().getTitle().compareTo(f1.getFeed().getTitle());
comparator = (f1, f2) -> feedTitle(f2).compareTo(feedTitle(f1));
break;
case RANDOM:
permutor = Collections::shuffle;
@ -103,6 +72,31 @@ public class QueueSorter {
return permutor;
}
// Null-safe accessors
@NonNull
private static Date pubDate(@Nullable FeedItem item) {
return (item != null && item.getPubDate() != null) ?
item.getPubDate() : new Date(0);
}
@NonNull
private static String itemTitle(@Nullable FeedItem item) {
return (item != null && item.getTitle() != null) ?
item.getTitle() : "";
}
private static int duration(@Nullable FeedItem item) {
return (item != null && item.getMedia() != null) ?
item.getMedia().getDuration() : 0;
}
@NonNull
private static String feedTitle(@Nullable FeedItem item) {
return (item != null && item.getFeed() != null && item.getFeed().getTitle() != null) ?
item.getFeed().getTitle() : "";
}
/**
* Implements a reordering by pubdate that avoids consecutive episodes from the same feed in
* the queue.

View File

@ -1,20 +1,42 @@
package de.danoeh.antennapod.core.util;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import static de.danoeh.antennapod.core.util.SortOrder.Scope.INTER_FEED;
import static de.danoeh.antennapod.core.util.SortOrder.Scope.INTRA_FEED;
/**
* Provides sort orders to sort a list of episodes.
*/
public enum SortOrder {
EPISODE_TITLE_A_Z,
EPISODE_TITLE_Z_A,
DATE_OLD_NEW,
DATE_NEW_OLD,
DURATION_SHORT_LONG,
DURATION_LONG_SHORT,
FEED_TITLE_A_Z,
FEED_TITLE_Z_A,
RANDOM,
SMART_SHUFFLE_OLD_NEW,
SMART_SHUFFLE_NEW_OLD;
DATE_OLD_NEW(1, INTRA_FEED),
DATE_NEW_OLD(2, INTRA_FEED),
EPISODE_TITLE_A_Z(3, INTRA_FEED),
EPISODE_TITLE_Z_A(4, INTRA_FEED),
DURATION_SHORT_LONG(5, INTRA_FEED),
DURATION_LONG_SHORT(6, INTRA_FEED),
FEED_TITLE_A_Z(101, INTER_FEED),
FEED_TITLE_Z_A(102, INTER_FEED),
RANDOM(103, INTER_FEED),
SMART_SHUFFLE_OLD_NEW(104, INTER_FEED),
SMART_SHUFFLE_NEW_OLD(105, INTER_FEED);
public enum Scope {
INTRA_FEED, INTER_FEED;
}
public final int code;
@NonNull
public final Scope scope;
SortOrder(int code, @NonNull Scope scope) {
this.code = code;
this.scope = scope;
}
/**
* Converts the string representation to its enum value. If the string value is unknown,
@ -27,4 +49,23 @@ public enum SortOrder {
return defaultValue;
}
}
@Nullable
public static SortOrder fromCodeString(@Nullable String codeStr) {
if (TextUtils.isEmpty(codeStr)) {
return null;
}
int code = Integer.parseInt(codeStr);
for (SortOrder sortOrder : values()) {
if (sortOrder.code == code) {
return sortOrder;
}
}
throw new IllegalArgumentException("Unsupported code: " + code);
}
@Nullable
public static String toCodeString(@Nullable SortOrder sortOrder) {
return sortOrder != null ? Integer.toString(sortOrder.code) : null;
}
}

View File

@ -7,7 +7,7 @@ import de.danoeh.antennapod.core.feed.FeedItem;
/** Compares the pubDate of two FeedItems for sorting*/
public class FeedItemPubdateComparator implements Comparator<FeedItem> {
/** Returns a new instance of this comparator in reverse order.
/** Returns a new instance of this comparator in reverse order.
public static FeedItemPubdateComparator newInstance() {
FeedItemPubdateComparator
}*/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 B

View File

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#FF757575" android:pathData="M15,19.88C15.04,20.18 14.94,20.5 14.71,20.71C14.32,21.1 13.69,21.1 13.3,20.71L9.29,16.7C9.06,16.47 8.96,16.16 9,15.87V10.75L4.21,4.62C3.87,4.19 3.95,3.56 4.38,3.22C4.57,3.08 4.78,3 5,3V3H19V3C19.22,3 19.43,3.08 19.62,3.22C20.05,3.56 20.13,4.19 19.79,4.62L15,10.75V19.88M7.04,5L11,10.06V15.58L13,17.58V10.05L16.96,5H7.04Z" />
</vector>

View File

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M15,19.88C15.04,20.18 14.94,20.5 14.71,20.71C14.32,21.1 13.69,21.1 13.3,20.71L9.29,16.7C9.06,16.47 8.96,16.16 9,15.87V10.75L4.21,4.62C3.87,4.19 3.95,3.56 4.38,3.22C4.57,3.08 4.78,3 5,3V3H19V3C19.22,3 19.43,3.08 19.62,3.22C20.05,3.56 20.13,4.19 19.79,4.62L15,10.75V19.88M7.04,5L11,10.06V15.58L13,17.58V10.05L16.96,5H7.04Z" />
</vector>

View File

@ -254,6 +254,25 @@
<item>is_favorite</item>
</string-array>
<!-- sort for podcast screen, not for queue -->
<string-array name="feed_episodes_sort_options">
<item>@string/sort_date_new_old</item>
<item>@string/sort_date_old_new</item>
<item>@string/sort_title_a_z</item>
<item>@string/sort_title_z_a</item>
<item>@string/sort_duration_short_long</item>
<item>@string/sort_duration_long_short</item>
</string-array>
<string-array name="feed_episodes_sort_values">
<item>DATE_NEW_OLD</item>
<item>DATE_OLD_NEW</item>
<item>EPISODE_TITLE_A_Z</item>
<item>EPISODE_TITLE_Z_A</item>
<item>DURATION_SHORT_LONG</item>
<item>DURATION_LONG_SHORT</item>
</string-array>
<string-array name="image_cache_size_options">
<item>20 MiB</item>
<item>50 MiB</item>

View File

@ -3,10 +3,13 @@ package de.danoeh.antennapod.core.feed;
import org.junit.Before;
import org.junit.Test;
import de.danoeh.antennapod.core.util.SortOrder;
import static de.danoeh.antennapod.core.feed.FeedMother.anyFeed;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class FeedTest {
@ -59,6 +62,27 @@ public class FeedTest {
feedImageWasUpdated();
}
@Test
public void testSetSortOrder_OnlyIntraFeedSortAllowed() throws Exception {
for (SortOrder sortOrder : SortOrder.values()) {
if (sortOrder.scope == SortOrder.Scope.INTRA_FEED) {
original.setSortOrder(sortOrder); // should be okay
} else {
try {
original.setSortOrder(sortOrder);
fail("SortOrder " + sortOrder + " should not be allowed on a feed");
} catch (IllegalArgumentException iae) {
// expected exception
}
}
}
}
@Test
public void testSetSortOrder_NullAllowed() throws Exception {
original.setSortOrder(null); // should be okay
}
private void feedHasNotChanged() {
assertFalse(original.compareWithOther(changedFeed));
}

View File

@ -10,22 +10,25 @@ import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNull;
/**
* Test class for QueueSorter.
* Test class for FeedItemPermutors.
*/
public class QueueSorterTest {
public class FeedItemPermutorsTest {
@Test
public void testPermutorForRule_null() {
assertNull(QueueSorter.getPermutor(null));
public void testEnsureNonNullPermutors() {
for (SortOrder sortOrder : SortOrder.values()) {
assertNotNull("The permutor for SortOrder " + sortOrder + " is unexpectedly null",
FeedItemPermutors.getPermutor(sortOrder));
}
}
@Test
public void testPermutorForRule_EPISODE_TITLE_ASC() {
Permutor<FeedItem> permutor = QueueSorter.getPermutor(SortOrder.EPISODE_TITLE_A_Z);
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.EPISODE_TITLE_A_Z);
List<FeedItem> itemList = getTestList();
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
@ -33,9 +36,22 @@ public class QueueSorterTest {
assertTrue(checkIdOrder(itemList, 1, 2, 3)); // after sorting
}
@Test
public void testPermutorForRule_EPISODE_TITLE_ASC_NullTitle() {
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.EPISODE_TITLE_A_Z);
List<FeedItem> itemList = getTestList();
itemList.get(2) // itemId 2
.setTitle(null);
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
permutor.reorder(itemList);
assertTrue(checkIdOrder(itemList, 2, 1, 3)); // after sorting
}
@Test
public void testPermutorForRule_EPISODE_TITLE_DESC() {
Permutor<FeedItem> permutor = QueueSorter.getPermutor(SortOrder.EPISODE_TITLE_Z_A);
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.EPISODE_TITLE_Z_A);
List<FeedItem> itemList = getTestList();
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
@ -45,7 +61,7 @@ public class QueueSorterTest {
@Test
public void testPermutorForRule_DATE_ASC() {
Permutor<FeedItem> permutor = QueueSorter.getPermutor(SortOrder.DATE_OLD_NEW);
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.DATE_OLD_NEW);
List<FeedItem> itemList = getTestList();
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
@ -53,9 +69,21 @@ public class QueueSorterTest {
assertTrue(checkIdOrder(itemList, 1, 2, 3)); // after sorting
}
@Test
public void testPermutorForRule_DATE_ASC_NulPubDatel() {
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.DATE_OLD_NEW);
List<FeedItem> itemList = getTestList();
itemList.get(2) // itemId 2
.setPubDate(null);
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
permutor.reorder(itemList);
assertTrue(checkIdOrder(itemList, 2, 1, 3)); // after sorting
}
@Test
public void testPermutorForRule_DATE_DESC() {
Permutor<FeedItem> permutor = QueueSorter.getPermutor(SortOrder.DATE_NEW_OLD);
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.DATE_NEW_OLD);
List<FeedItem> itemList = getTestList();
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
@ -65,7 +93,7 @@ public class QueueSorterTest {
@Test
public void testPermutorForRule_DURATION_ASC() {
Permutor<FeedItem> permutor = QueueSorter.getPermutor(SortOrder.DURATION_SHORT_LONG);
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.DURATION_SHORT_LONG);
List<FeedItem> itemList = getTestList();
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
@ -75,7 +103,7 @@ public class QueueSorterTest {
@Test
public void testPermutorForRule_DURATION_DESC() {
Permutor<FeedItem> permutor = QueueSorter.getPermutor(SortOrder.DURATION_LONG_SHORT);
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.DURATION_LONG_SHORT);
List<FeedItem> itemList = getTestList();
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
@ -83,9 +111,21 @@ public class QueueSorterTest {
assertTrue(checkIdOrder(itemList, 3, 2, 1)); // after sorting
}
@Test
public void testPermutorForRule_DURATION_DESC_NullMedia() {
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.DURATION_LONG_SHORT);
List<FeedItem> itemList = getTestList();
itemList.get(1) // itemId 3
.setMedia(null);
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
permutor.reorder(itemList);
assertTrue(checkIdOrder(itemList, 2, 1, 3)); // after sorting
}
@Test
public void testPermutorForRule_FEED_TITLE_ASC() {
Permutor<FeedItem> permutor = QueueSorter.getPermutor(SortOrder.FEED_TITLE_A_Z);
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.FEED_TITLE_A_Z);
List<FeedItem> itemList = getTestList();
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
@ -95,7 +135,7 @@ public class QueueSorterTest {
@Test
public void testPermutorForRule_FEED_TITLE_DESC() {
Permutor<FeedItem> permutor = QueueSorter.getPermutor(SortOrder.FEED_TITLE_Z_A);
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.FEED_TITLE_Z_A);
List<FeedItem> itemList = getTestList();
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
@ -103,6 +143,18 @@ public class QueueSorterTest {
assertTrue(checkIdOrder(itemList, 3, 2, 1)); // after sorting
}
@Test
public void testPermutorForRule_FEED_TITLE_DESC_NullTitle() {
Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(SortOrder.FEED_TITLE_Z_A);
List<FeedItem> itemList = getTestList();
itemList.get(1) // itemId 3
.getFeed().setTitle(null);
assertTrue(checkIdOrder(itemList, 1, 3, 2)); // before sorting
permutor.reorder(itemList);
assertTrue(checkIdOrder(itemList, 2, 1, 3)); // after sorting
}
/**
* Generates a list with test data.
*/