Merge pull request #974 from mfietz/feature/episodes-actions

Perform action on episodes
This commit is contained in:
Tom Hennen 2015-06-28 16:27:41 -04:00
commit cff363e474
9 changed files with 669 additions and 22 deletions

View File

@ -0,0 +1,416 @@
package de.danoeh.antennapod.dialog;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
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 android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import com.joanzapata.android.iconify.IconDrawable;
import com.joanzapata.android.iconify.Iconify;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.util.LongList;
public class EpisodesApplyActionFragment extends Fragment {
public String TAG = "EpisodeActionFragment";
private ListView mListView;
private ArrayAdapter<String> mAdapter;
private Button btnAddToQueue;
private Button btnMarkAsPlayed;
private Button btnMarkAsUnplayed;
private Button btnDownload;
private Button btnDelete;
private final Map<Long,FeedItem> idMap;
private final List<FeedItem> episodes;
private final List<String> titles = new ArrayList();
private final LongList checkedIds = new LongList();
private MenuItem mSelectToggle;
private int textColor;
public EpisodesApplyActionFragment(List<FeedItem> episodes) {
this.episodes = episodes;
this.idMap = new HashMap<>(episodes.size());
for(FeedItem episode : episodes) {
this.idMap.put(episode.getId(), episode);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.episodes_apply_action_fragment, container, false);
mListView = (ListView) view.findViewById(android.R.id.list);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> ListView, View view, int position, long rowId) {
long id = episodes.get(position).getId();
if (checkedIds.contains(id)) {
checkedIds.remove(id);
} else {
checkedIds.add(id);
}
refreshCheckboxes();
}
});
for(FeedItem episode : episodes) {
titles.add(episode.getTitle());
}
mAdapter = new ArrayAdapter<>(getActivity(),
android.R.layout.simple_list_item_multiple_choice, titles);
mListView.setAdapter(mAdapter);
checkAll();
btnAddToQueue = (Button) view.findViewById(R.id.btnAddToQueue);
btnAddToQueue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
queueChecked();
}
});
btnMarkAsPlayed = (Button) view.findViewById(R.id.btnMarkAsPlayed);
btnMarkAsPlayed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
markedCheckedPlayed();
}
});
btnMarkAsUnplayed = (Button) view.findViewById(R.id.btnMarkAsUnplayed);
btnMarkAsUnplayed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
markedCheckedUnplayed();
}
});
btnDownload = (Button) view.findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downloadChecked();
}
});
btnDelete = (Button) view.findViewById(R.id.btnDelete);
btnDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
deleteChecked();
}
});
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.episodes_apply_action_options, menu);
int[] attrs = { android.R.attr.textColor };
TypedArray ta = getActivity().obtainStyledAttributes(attrs);
textColor = ta.getColor(0, Color.GRAY);
ta.recycle();
menu.findItem(R.id.sort).setIcon(new IconDrawable(getActivity(),
Iconify.IconValue.fa_sort).color(textColor).actionBarSize());
mSelectToggle = menu.findItem(R.id.select_toggle);
mSelectToggle.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (checkedIds.size() == episodes.size()) {
checkNone();
} else {
checkAll();
}
return true;
}
});
menu.findItem(R.id.select_options).setIcon(new IconDrawable(getActivity(),
Iconify.IconValue.fa_caret_down).color(textColor).actionBarSize());
}
@Override
public void onPrepareOptionsMenu (Menu menu) {
Iconify.IconValue iVal;
if(checkedIds.size() == episodes.size()) {
iVal = Iconify.IconValue.fa_check_square_o;
} else if(checkedIds.size() == 0) {
iVal = Iconify.IconValue.fa_square_o;
} else {
iVal = Iconify.IconValue.fa_minus_square_o;
}
mSelectToggle.setIcon(new IconDrawable(getActivity(), iVal).color(textColor).actionBarSize());
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int resId = 0;
switch(item.getItemId()) {
case R.id.select_options:
return true;
case R.id.check_all:
checkAll();
resId = R.string.selected_all_label;
break;
case R.id.check_none:
checkNone();
resId = R.string.deselected_all_label;
break;
case R.id.check_played:
checkPlayed(true);
resId = R.string.selected_played_label;
break;
case R.id.check_unplayed:
checkPlayed(false);
resId = R.string.selected_unplayed_label;
break;
case R.id.check_downloaded:
checkDownloaded(true);
resId = R.string.selected_downloaded_label;
break;
case R.id.check_not_downloaded:
checkDownloaded(false);
resId = R.string.selected_not_downloaded_label;
break;
case R.id.sort_title_a_z:
sortByTitle(false);
return true;
case R.id.sort_title_z_a:
sortByTitle(true);
return true;
case R.id.sort_date_new_old:
sortByDate(true);
return true;
case R.id.sort_date_old_new:
sortByDate(false);
return true;
case R.id.sort_duration_long_short:
sortByDuration(true);
return true;
case R.id.sort_duration_short_long:
sortByDuration(false);
return true;
}
if(resId != 0) {
Toast.makeText(getActivity(), resId, Toast.LENGTH_SHORT).show();
return true;
} else {
return false;
}
}
private void sortByTitle(final boolean reverse) {
Collections.sort(episodes, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
if (reverse) {
return -1 * lhs.getTitle().compareTo(rhs.getTitle());
} else {
return lhs.getTitle().compareTo(rhs.getTitle());
}
}
});
refreshTitles();
refreshCheckboxes();
}
private void sortByDate(final boolean reverse) {
Collections.sort(episodes, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
if (lhs.getPubDate() == null) {
return -1;
} else if (rhs.getPubDate() == null) {
return 1;
}
int code = lhs.getPubDate().compareTo(rhs.getPubDate());
if (reverse) {
return -1 * code;
} else {
return code;
}
}
});
refreshTitles();
refreshCheckboxes();
}
private void sortByDuration(final boolean reverse) {
Collections.sort(episodes, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
int ordering;
if (false == lhs.hasMedia()) {
ordering = 1;
} else if (false == rhs.hasMedia()) {
ordering = -1;
} else {
ordering = lhs.getMedia().getDuration() - rhs.getMedia().getDuration();
}
if(reverse) {
return -1 * ordering;
} else {
return ordering;
}
}
});
refreshTitles();
refreshCheckboxes();
}
private void checkAll() {
for (FeedItem episode : episodes) {
if(false == checkedIds.contains(episode.getId())) {
checkedIds.add(episode.getId());
}
}
refreshCheckboxes();
}
private void checkNone() {
checkedIds.clear();
refreshCheckboxes();
}
private void checkPlayed(boolean isPlayed) {
for (FeedItem episode : episodes) {
if(episode.isRead() == isPlayed) {
if(!checkedIds.contains(episode.getId())) {
checkedIds.add(episode.getId());
}
} else {
if(checkedIds.contains(episode.getId())) {
checkedIds.remove(episode.getId());
}
}
}
refreshCheckboxes();
}
private void checkDownloaded(boolean isDownloaded) {
for (FeedItem episode : episodes) {
if(episode.hasMedia() && episode.getMedia().isDownloaded() == isDownloaded) {
if(!checkedIds.contains(episode.getId())) {
checkedIds.add(episode.getId());
}
} else {
if(checkedIds.contains(episode.getId())) {
checkedIds.remove(episode.getId());
}
}
}
refreshCheckboxes();
}
private void refreshTitles() {
titles.clear();
for(FeedItem episode : episodes) {
titles.add(episode.getTitle());
}
mAdapter.notifyDataSetChanged();
}
private void refreshCheckboxes() {
for (int i = 0; i < episodes.size(); i++) {
FeedItem episode = episodes.get(i);
boolean checked = checkedIds.contains(episode.getId());
mListView.setItemChecked(i, checked);
}
ActivityCompat.invalidateOptionsMenu(EpisodesApplyActionFragment.this.getActivity());
}
private void queueChecked() {
LongList orderedIds = new LongList();
for(FeedItem episode : episodes) {
if(checkedIds.contains(episode.getId())) {
orderedIds.add((episode.getId()));
}
}
DBWriter.addQueueItem(getActivity(), false, orderedIds.toArray());
close();
}
private void markedCheckedPlayed() {
DBWriter.markItemRead(getActivity(), true, checkedIds.toArray());
close();
}
private void markedCheckedUnplayed() {
DBWriter.markItemRead(getActivity(), false, checkedIds.toArray());
close();
}
private void downloadChecked() {
// download the check episodes in the same order as they are currently displayed
List<FeedItem> toDownload = new ArrayList<FeedItem>(checkedIds.size());
for(FeedItem episode : episodes) {
if(checkedIds.contains(episode.getId())) {
toDownload.add(episode);
}
}
try {
DBTasks.downloadFeedItems(getActivity(), toDownload.toArray(new FeedItem[0]));
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage());
}
close();
}
private void deleteChecked() {
for(long id : checkedIds.toArray()) {
FeedItem episode = idMap.get(id);
if(episode.hasMedia()) {
DBWriter.deleteFeedMediaOfItem(getActivity(), episode.getMedia().getId());
}
}
close();
}
private void close() {
getActivity().getSupportFragmentManager().popBackStack();
}
}

View File

@ -4,13 +4,15 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.ListFragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.util.Log;
@ -29,6 +31,7 @@ import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.joanzapata.android.iconify.IconDrawable;
import com.joanzapata.android.iconify.Iconify;
import com.squareup.picasso.Picasso;
@ -61,6 +64,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
@ -156,6 +160,7 @@ public class ItemlistFragment extends ListFragment {
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
updateProgressBarVisibility();
startItemLoader();
}
@ -222,6 +227,13 @@ public class ItemlistFragment extends ListFragment {
menu.findItem(R.id.share_link_item).setVisible(false);
menu.findItem(R.id.visit_website_item).setVisible(false);
}
int[] attrs = { android.R.attr.textColor };
TypedArray ta = getActivity().obtainStyledAttributes(attrs);
int textColor = ta.getColor(0, Color.GRAY);
ta.recycle();
menu.findItem(R.id.episode_actions).setIcon(new IconDrawable(getActivity(),
Iconify.IconValue.fa_gears).color(textColor).actionBarSize());
isUpdatingFeed = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
}
@ -240,6 +252,10 @@ public class ItemlistFragment extends ListFragment {
try {
if (!FeedMenuHandler.onOptionsItemClicked(getActivity(), item, feed)) {
switch (item.getItemId()) {
case R.id.episode_actions:
Fragment fragment = new EpisodesApplyActionFragment(feed.getItems());
((MainActivity)getActivity()).loadChildFragment(fragment);
return true;
case R.id.remove_item:
final FeedRemover remover = new FeedRemover(
getActivity(), feed) {
@ -406,6 +422,9 @@ public class ItemlistFragment extends ListFragment {
private boolean insideOnFragmentLoaded = false;
private void onFragmentLoaded() {
if(!isVisible()) {
return;
}
insideOnFragmentLoaded = true;
if (adapter == null) {
setListAdapter(null);

View File

@ -72,7 +72,7 @@ public class NewEpisodesFragment extends AllEpisodesFragment {
Log.d(TAG, "remove(" + which + ")");
stopItemLoader();
FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
DBWriter.markItemRead(getActivity(), item.getId(), true);
DBWriter.markItemRead(getActivity(), true, item.getId());
undoBarController.showUndoBar(false,
getString(R.string.marked_as_read_label), new FeedItemUndoToken(item,
which)
@ -88,7 +88,7 @@ public class NewEpisodesFragment extends AllEpisodesFragment {
public void onUndo(FeedItemUndoToken token) {
if (token != null) {
long itemId = token.getFeedItemId();
DBWriter.markItemRead(context, itemId, false);
DBWriter.markItemRead(context, false, itemId);
}
}
@Override

View File

@ -41,7 +41,6 @@ public class FeedMenuHandler {
}
Log.d(TAG, "Preparing options menu");
menu.findItem(R.id.mark_all_read_item).setVisible(selectedFeed.hasNewItems());
if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable()) {
menu.findItem(R.id.support_item).setVisible(true);
} else {

View File

@ -0,0 +1,120 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/bottomBar"
android:layout_width="wrap_content"
android:layout_height="68dp"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="4dp">
<Button
android:id="@+id/btnAddToQueue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="?attr/content_new"
android:text="@string/add_to_queue_label"
android:textSize="10sp"
android:background="@android:color/transparent"/>
<View xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:background="?android:attr/listDivider"
tools:background="@android:color/holo_red_dark" />
<Button
android:id="@+id/btnMarkAsPlayed"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="?attr/navigation_accept"
android:text="@string/mark_read_label"
android:textSize="10sp"
android:background="@android:color/transparent"/>
<View xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:background="?android:attr/listDivider"
tools:background="@android:color/holo_red_dark" />
<Button
android:id="@+id/btnMarkAsUnplayed"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="?attr/navigation_cancel"
android:text="@string/mark_unread_label"
android:textSize="10sp"
android:background="@android:color/transparent"/>
<View xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:background="?android:attr/listDivider"
tools:background="@android:color/holo_red_dark" />
<Button
android:id="@+id/btnDownload"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="?attr/av_download"
android:text="@string/download_label"
android:textSize="10sp"
android:background="@android:color/transparent"/>
<View xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:background="?android:attr/listDivider"
tools:background="@android:color/holo_red_dark" />
<Button
android:id="@+id/btnDelete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="?attr/content_discard"
android:text="@string/remove_episode_lable"
android:textSize="10sp"
android:background="@android:color/transparent"/>
</LinearLayout>
<View
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
android:paddingBottom="4dp"
android:layout_above="@id/bottomBar"
tools:background="@android:color/holo_red_dark" />
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/divider">
</ListView>
</RelativeLayout>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/sort"
android:title="@string/sort"
app:showAsAction="always">
<menu>
<item android:id="@+id/sort_title"
android:title="@string/sort_title"/>
<item android:id="@+id/sort_title_a_z"
android:title="@string/sort_title_a_z"/>
<item android:id="@+id/sort_title_z_a"
android:title="@string/sort_title_z_a"/>
<item android:id="@+id/sort_date_new_old"
android:title="@string/sort_date_new_old"/>
<item android:id="@+id/sort_date_old_new"
android:title="@string/sort_date_old_new"/>
<item android:id="@+id/sort_duration_short_long"
android:title="@string/sort_duration_short_long"/>
<item android:id="@+id/sort_duration_long_short"
android:title="@string/sort_duration_long_short"/>
</menu>
</item>
<item
android:id="@+id/select_toggle"
android:title="@string/select_all_label"
app:showAsAction="always"/>
<item
android:id="@+id/select_options"
android:title="@string/all_label"
app:showAsAction="always">
<menu>
<item android:id="@+id/select_label"
android:title="@string/select_label"/>
<item android:id="@+id/check_all"
android:title="@string/all_label"/>
<item android:id="@+id/check_none"
android:title="@string/none_label"/>
<item android:id="@+id/check_played"
android:title="@string/played_label"/>
<item android:id="@+id/check_unplayed"
android:title="@string/unplayed_label"/>
<item android:id="@+id/check_downloaded"
android:title="@string/downloaded_label"/>
<item android:id="@+id/check_not_downloaded"
android:title="@string/not_downloaded_label"/>
</menu>
</item>
</menu>

View File

@ -9,6 +9,12 @@
android:title="@string/hide_episodes_title"
custom:showAsAction="always">
</item>
<item
android:id="@+id/episode_actions"
android:menuCategory="container"
android:title="@string/episode_actions"
custom:showAsAction="always">
</item>
<item
android:id="@+id/refresh_item"
android:icon="?attr/navigation_refresh"
@ -30,13 +36,6 @@
custom:actionViewClass="android.support.v7.widget.SearchView"
android:title="@string/search_label"/>
<item
android:id="@+id/mark_all_read_item"
android:menuCategory="container"
android:title="@string/mark_all_read_label"
custom:showAsAction="collapseActionView">
</item>
<item
android:id="@+id/support_item"
android:menuCategory="container"

View File

@ -375,14 +375,21 @@ public class DBWriter {
}
public static Future<?> addQueueItem(final Context context,
final long... itemIds) {
return addQueueItem(context, false, itemIds);
}
/**
* Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true.
* If a FeedItem is already in the queue, the FeedItem will not change its position in the queue.
*
* @param context A context that is used for opening a database connection.
* @param performAutoDownload true if an auto-download process should be started after the operation.
* @param itemIds IDs of the FeedItem objects that should be added to the queue.
*/
public static Future<?> addQueueItem(final Context context,
public static Future<?> addQueueItem(final Context context, final boolean performAutoDownload,
final long... itemIds) {
return dbExec.submit(new Runnable() {
@ -408,7 +415,7 @@ public class DBWriter {
boolean addToFront = UserPreferences.enqueueAtFront();
if(addToFront){
queue.add(0, item);
queue.add(0+i, item);
} else {
queue.add(item);
}
@ -423,11 +430,12 @@ public class DBWriter {
}
}
adapter.close();
DBTasks.autodownloadUndownloadedItems(context);
if (performAutoDownload) {
DBTasks.autodownloadUndownloadedItems(context);
}
}
}
});
}
/**
@ -595,16 +603,24 @@ public class DBWriter {
adapter.close();
}
/**
* Sets the 'read'-attribute of a FeedItem to the specified value.
/*
* Sets the 'read'-attribute of all specified FeedItems
*
* @param context A context that is used for opening a database connection.
* @param itemId ID of the FeedItem
* @param read New value of the 'read'-attribute
* @param itemIds IDs of the FeedItems.
*/
public static Future<?> markItemRead(final Context context, final long itemId,
final boolean read) {
return markItemRead(context, itemId, read, 0, false);
public static Future<?> markItemRead(final Context context, final boolean read, final long... itemIds) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setFeedItemRead(read, itemIds);
adapter.close();
EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
});
}

View File

@ -96,6 +96,7 @@
<string name="feed_remover_msg">Removing Feed</string>
<string name="load_complete_feed">Refresh complete Feed</string>
<string name="hide_episodes_title">Hide Episodes</string>
<string name="episode_actions">Apply actions</string>
<string name="hide_unplayed_episodes_label">Unplayed</string>
<string name="hide_paused_episodes_label">Paused</string>
<string name="hide_played_episodes_label">Played</string>
@ -115,8 +116,8 @@
<string name="remove_label">Remove</string>
<string name="remove_episode_lable">Remove Episode</string>
<string name="mark_read_label">Mark as played</string>
<string name="mark_unread_label">Mark as unplayed</string>
<string name="marked_as_read_label">Marked as played</string>
<string name="mark_unread_label">Mark as unplayed</string>
<string name="add_to_queue_label">Add to Queue</string>
<string name="added_to_queue_label">Added to Queue</string>
<string name="remove_from_queue_label">Remove from Queue</string>
@ -346,6 +347,7 @@
<string name="opml_import_error_dir_empty">The import directory is empty.</string>
<string name="select_all_label">Select all</string>
<string name="deselect_all_label">Deselect all</string>
<string name="select_options_label">Select ...</string>
<string name="choose_file_from_filesystem">From local filesystem</string>
<string name="choose_file_from_external_application">Use external application</string>
<string name="opml_export_label">OPML export</string>
@ -450,4 +452,25 @@
<string name="sp_apps_importing_feeds_msg">Importing subscriptions from single-purpose apps&#8230;</string>
<string name="search_itunes_label">Search iTunes</string>
<string name="select_label"><b>Select ...</b></string>
<string name="all_label">All</string>
<string name="selected_all_label">Selected all Episodes</string>
<string name="none_label">None</string>
<string name="deselected_all_label">Deselected all Episodes</string>
<string name="played_label">Played</string>
<string name="selected_played_label">Selected played Episodes</string>
<string name="unplayed_label">Unplayed</string>
<string name="selected_unplayed_label">Selected unplayed Episodes</string>
<string name="downloaded_label">Downloaded</string>
<string name="selected_downloaded_label">Selected downloaded Episodes</string>
<string name="not_downloaded_label">Not downloaded</string>
<string name="selected_not_downloaded_label">Selected not downloaded Episodes</string>
<string name="sort_title"><b>Sort by ...</b></string>
<string name="sort_title_a_z">Title (A \u2192 Z)</string>
<string name="sort_title_z_a">Title (Z \u2192 A)</string>
<string name="sort_date_new_old">Date (New \u2192 Old)</string>
<string name="sort_date_old_new">Date (Old \u2192 New)</string>
<string name="sort_duration_short_long">Duration (Short \u2192 Long)</string>
<string name="sort_duration_long_short">Duration (Long \u2192 Short)</string>
</resources>