Merge pull request #808 from mfietz/feature/nav-indicators+new-redone

Nav indicators, context menus and new "new"
This commit is contained in:
Tom Hennen 2015-05-20 18:20:03 -04:00
commit ba036e1499
46 changed files with 1607 additions and 514 deletions

View File

@ -45,7 +45,7 @@ public class DBReaderTest extends InstrumentationTestCase {
private void expiredFeedListTestHelper(long lastUpdate, long expirationTime, boolean shouldReturn) {
final Context context = getInstrumentation().getTargetContext();
Feed feed = new Feed(0, new Date(lastUpdate), "feed", "link", "descr", null,
null, null, null, "feed", null, null, "url", false, new FlattrStatus(), false, null, null);
null, null, null, "feed", null, null, "url", false, new FlattrStatus(), false, null, null, false);
feed.setItems(new ArrayList<FeedItem>());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
@ -301,7 +301,7 @@ public class DBReaderTest extends InstrumentationTestCase {
}
}
public void testGetUnreadItemIds() {
public void testGetNewItemIds() {
final Context context = getInstrumentation().getTargetContext();
final int numItems = 10;
@ -310,10 +310,11 @@ public class DBReaderTest extends InstrumentationTestCase {
for (int i = 0; i < unread.size(); i++) {
unreadIds[i] = unread.get(i).getId();
}
long[] unreadSaved = DBReader.getUnreadItemIds(context);
LongList unreadSaved = DBReader.getNewItemIds(context);
assertNotNull(unreadSaved);
assertTrue(unread.size() == unreadSaved.length);
for (long savedId : unreadSaved) {
assertTrue(unread.size() == unreadSaved.size());
for(int i=0; i < unreadSaved.size(); i++) {
long savedId = unreadSaved.get(i);
boolean found = false;
for (long id : unreadIds) {
if (id == savedId) {
@ -375,7 +376,7 @@ public class DBReaderTest extends InstrumentationTestCase {
List<Feed> feeds = DBTestUtils.saveFeedlist(context, NUM_FEEDS, NUM_ITEMS, true);
DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData(context);
assertEquals(NUM_FEEDS, navDrawerData.feeds.size());
assertEquals(0, navDrawerData.numUnreadItems);
assertEquals(0, navDrawerData.numNewItems);
assertEquals(0, navDrawerData.queueSize);
}
@ -404,7 +405,7 @@ public class DBReaderTest extends InstrumentationTestCase {
DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData(context);
assertEquals(NUM_FEEDS, navDrawerData.feeds.size());
assertEquals(NUM_UNREAD, navDrawerData.numUnreadItems);
assertEquals(NUM_UNREAD, navDrawerData.numNewItems);
assertEquals(NUM_QUEUE, navDrawerData.queueSize);
}

View File

@ -302,7 +302,7 @@ public class DBTasksTest extends InstrumentationTestCase {
private void expiredFeedListTestHelper(long lastUpdate, long expirationTime, boolean shouldReturn) {
UserPreferences.setUpdateInterval(context, expirationTime);
Feed feed = new Feed(0, new Date(lastUpdate), "feed", "link", "descr", null,
null, null, null, "feed", null, null, "url", false, new FlattrStatus(), false, null, null);
null, null, null, "feed", null, null, "url", false, new FlattrStatus(), false, null, null, false);
feed.setItems(new ArrayList<FeedItem>());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();

View File

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

View File

@ -703,8 +703,13 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
}
@Override
public int getNumberOfUnreadItems() {
return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
public int getNumberOfNewItems() {
return (navDrawerData != null) ? navDrawerData.numNewItems : 0;
}
@Override
public int getNumberOfUnreadFeedItems(long feedId) {
return (navDrawerData != null) ? navDrawerData.numUnreadFeedItems.get(feedId) : 0;
}
};
}

View File

@ -507,8 +507,13 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
@Override
public int getNumberOfUnreadItems() {
return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
public int getNumberOfNewItems() {
return (navDrawerData != null) ? navDrawerData.numNewItems : 0;
}
@Override
public int getNumberOfUnreadFeedItems(long feedId) {
return (navDrawerData != null) ? navDrawerData.numUnreadFeedItems.get(feedId) : 0;
}
};

View File

@ -22,19 +22,22 @@ import de.danoeh.antennapod.core.util.Converter;
/**
* List adapter for the list of new episodes
*/
public class NewEpisodesListAdapter extends BaseAdapter {
public class AllEpisodesListAdapter extends BaseAdapter {
private final Context context;
private final ItemAccess itemAccess;
private final ActionButtonCallback actionButtonCallback;
private final ActionButtonUtils actionButtonUtils;
private final boolean showOnlyNewEpisodes;
public NewEpisodesListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback) {
public AllEpisodesListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback,
boolean showOnlyNewEpisodes) {
super();
this.context = context;
this.itemAccess = itemAccess;
this.actionButtonUtils = new ActionButtonUtils(context);
this.actionButtonCallback = actionButtonCallback;
this.showOnlyNewEpisodes = showOnlyNewEpisodes;
}
@Override
@ -88,7 +91,7 @@ public class NewEpisodesListAdapter extends BaseAdapter {
holder.title.setText(item.getTitle());
holder.pubDate.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL));
if (item.isRead()) {
if (showOnlyNewEpisodes || item.isRead() || false == itemAccess.isNew(item)) {
holder.statusUnread.setVisibility(View.INVISIBLE);
} else {
holder.statusUnread.setVisibility(View.VISIBLE);
@ -175,5 +178,8 @@ public class NewEpisodesListAdapter extends BaseAdapter {
int getItemDownloadProgressPercent(FeedItem item);
boolean isInQueue(FeedItem item);
boolean isNew(FeedItem item);
}
}

View File

@ -14,6 +14,8 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.nineoldandroids.view.ViewHelper;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@ -110,22 +112,15 @@ public class FeedItemlistAdapter extends BaseAdapter {
}
holder.title.setText(buffer.toString());
FeedItem.State state = item.getState();
switch (state) {
case PLAYING:
holder.statusUnread.setVisibility(View.INVISIBLE);
holder.episodeProgress.setVisibility(View.VISIBLE);
break;
case IN_PROGRESS:
holder.statusUnread.setVisibility(View.INVISIBLE);
holder.episodeProgress.setVisibility(View.VISIBLE);
break;
case NEW:
holder.statusUnread.setVisibility(View.VISIBLE);
break;
default:
holder.statusUnread.setVisibility(View.INVISIBLE);
break;
if(false == item.isRead() && itemAccess.isNew(item)) {
holder.statusUnread.setVisibility(View.VISIBLE);
} else {
holder.statusUnread.setVisibility(View.INVISIBLE);
}
if(item.isRead()) {
ViewHelper.setAlpha(convertView, 0.5f);
} else {
ViewHelper.setAlpha(convertView, 1.0f);
}
holder.published.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL));
@ -151,10 +146,10 @@ public class FeedItemlistAdapter extends BaseAdapter {
item.getMedia())) {
holder.episodeProgress.setVisibility(View.VISIBLE);
holder.episodeProgress.setProgress(((ItemAccess) itemAccess).getItemDownloadProgressPercent(item));
holder.published.setVisibility(View.GONE);
} else {
holder.episodeProgress.setVisibility(View.GONE);
holder.published.setVisibility(View.VISIBLE);
if(media.getPosition() == 0) {
holder.episodeProgress.setVisibility(View.GONE);
}
}
TypedArray typeDrawables = context.obtainStyledAttributes(
@ -217,6 +212,7 @@ public class FeedItemlistAdapter extends BaseAdapter {
}
public interface ItemAccess {
boolean isInQueue(FeedItem item);
int getItemDownloadProgressPercent(FeedItem item);
@ -224,6 +220,9 @@ public class FeedItemlistAdapter extends BaseAdapter {
int getCount();
FeedItem getItem(int position);
boolean isNew(FeedItem item);
}
}

View File

@ -10,6 +10,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.IconTextView;
import android.widget.ImageView;
import android.widget.TextView;
@ -190,9 +191,9 @@ public class NavListAdapter extends BaseAdapter
convertView = inflater.inflate(R.layout.nav_listitem, parent, false);
holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.count = (TextView) convertView.findViewById(R.id.txtvCount);
holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
convertView.setTag(holder);
} else {
holder = (NavHolder) convertView.getTag();
@ -209,7 +210,7 @@ public class NavListAdapter extends BaseAdapter
holder.count.setVisibility(View.GONE);
}
} else if (tags.get(position).equals(NewEpisodesFragment.TAG)) {
int unreadItems = itemAccess.getNumberOfUnreadItems();
int unreadItems = itemAccess.getNumberOfNewItems();
if (unreadItems > 0) {
holder.count.setVisibility(View.VISIBLE);
holder.count.setText(String.valueOf(unreadItems));
@ -248,45 +249,59 @@ public class NavListAdapter extends BaseAdapter
convertView = inflater.inflate(R.layout.nav_feedlistitem, parent, false);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.failure = (IconTextView) convertView.findViewById(R.id.itxtvFailure);
holder.count = (TextView) convertView.findViewById(R.id.txtvCount);
convertView.setTag(holder);
} else {
holder = (FeedHolder) convertView.getTag();
}
holder.title.setText(feed.getTitle());
Picasso.with(context)
.load(feed.getImageUri())
.fit()
.into(holder.image);
holder.title.setText(feed.getTitle());
if(feed.hasLastUpdateFailed()) {
holder.failure.setVisibility(View.VISIBLE);
} else {
holder.failure.setVisibility(View.GONE);
}
int feedUnreadItems = itemAccess.getNumberOfUnreadFeedItems(feed.getId());
if(feedUnreadItems > 0) {
holder.count.setVisibility(View.VISIBLE);
holder.count.setText(String.valueOf(feedUnreadItems));
holder.count.setTypeface(holder.title.getTypeface());
} else {
holder.count.setVisibility(View.INVISIBLE);
}
return convertView;
}
static class NavHolder {
ImageView image;
TextView title;
TextView count;
ImageView image;
}
static class FeedHolder {
TextView title;
ImageView image;
TextView title;
IconTextView failure;
TextView count;
}
public interface ItemAccess {
public int getCount();
public Feed getItem(int position);
public int getSelectedItemIndex();
public int getQueueSize();
public int getNumberOfUnreadItems();
int getCount();
Feed getItem(int position);
int getSelectedItemIndex();
int getQueueSize();
int getNumberOfNewItems();
int getNumberOfUnreadFeedItems(long feedId);
}
}

View File

@ -125,6 +125,7 @@ public class StorageCallbacksImpl implements StorageCallbacks {
PodDBAdapter.KEY_CHAPTER_TYPE));
}
if(oldVersion <= 14) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " INTEGER");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
@ -133,8 +134,20 @@ public class StorageCallbacksImpl implements StorageCallbacks {
+ " FROM " + PodDBAdapter.TABLE_NAME_FEEDS
+ " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_ID
+ " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + ")");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_HIDE + " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0");
// create indexes
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_FEED);
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_IMAGE);
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDMEDIA_FEEDITEM);
db.execSQL(PodDBAdapter.CREATE_INDEX_QUEUE_FEEDITEM);
db.execSQL(PodDBAdapter.CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
}
}
}

View File

@ -10,6 +10,8 @@ import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -29,7 +31,7 @@ import java.util.concurrent.atomic.AtomicReference;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
import de.danoeh.antennapod.adapter.NewEpisodesListAdapter;
import de.danoeh.antennapod.adapter.AllEpisodesListAdapter;
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.feed.EventDistributor;
@ -41,11 +43,11 @@ import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
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.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
/**
* Shows unread or recently published episodes
@ -66,18 +68,19 @@ public class AllEpisodesFragment extends Fragment {
private String prefName;
protected DragSortListView listView;
private NewEpisodesListAdapter listAdapter;
private AllEpisodesListAdapter listAdapter;
private TextView txtvEmpty;
private ProgressBar progLoading;
private ContextMenu contextMenu;
private List<FeedItem> unreadItems;
private List<FeedItem> recentItems;
private LongList queueAccess;
private List<FeedItem> episodes;
private LongList queuedItemsIds;
private LongList newItemsIds;
private List<Downloader> downloaderList;
private boolean itemsLoaded = false;
private boolean viewsCreated = false;
private boolean showOnlyNewEpisodes = false;
private final boolean showOnlyNewEpisodes;
private AtomicReference<MainActivity> activity = new AtomicReference<MainActivity>();
@ -225,7 +228,7 @@ public class AllEpisodesFragment extends Fragment {
if (itemsLoaded) {
MenuItem menuItem = menu.findItem(R.id.mark_all_read_item);
if (menuItem != null) {
menuItem.setVisible(unreadItems != null && !unreadItems.isEmpty());
menuItem.setVisible(episodes != null && !episodes.isEmpty());
}
}
}
@ -295,6 +298,8 @@ public class AllEpisodesFragment extends Fragment {
}
});
registerForContextMenu(listView);
if (!itemsLoaded) {
progLoading.setVisibility(View.VISIBLE);
txtvEmpty.setVisibility(View.GONE);
@ -309,9 +314,59 @@ public class AllEpisodesFragment extends Fragment {
return root;
}
private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
if(contextMenu == null) {
return;
}
MenuItem item = contextMenu.findItem(id);
if (item != null) {
item.setVisible(visible);
}
}
};
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
FeedItem item = itemAccess.getItem(adapterInfo.position);
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.allepisodes_context, menu);
if (item != null) {
menu.setHeaderTitle(item.getTitle());
}
contextMenu = menu;
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queuedItemsIds);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
FeedItem selectedItem = itemAccess.getItem(menuInfo.position);
if (selectedItem == null) {
Log.i(TAG, "Selected item at position " + menuInfo.position + " was null, ignoring selection");
return super.onContextItemSelected(item);
}
try {
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
return true;
}
}
private void onFragmentLoaded() {
if (listAdapter == null) {
listAdapter = new NewEpisodesListAdapter(activity.get(), itemAccess, new DefaultActionButtonCallback(activity.get()));
listAdapter = new AllEpisodesListAdapter(activity.get(), itemAccess,
new DefaultActionButtonCallback(activity.get()), showOnlyNewEpisodes);
listView.setAdapter(listAdapter);
listView.setEmptyView(txtvEmpty);
downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback);
@ -340,12 +395,12 @@ public class AllEpisodesFragment extends Fragment {
}
};
private NewEpisodesListAdapter.ItemAccess itemAccess = new NewEpisodesListAdapter.ItemAccess() {
private AllEpisodesListAdapter.ItemAccess itemAccess = new AllEpisodesListAdapter.ItemAccess() {
@Override
public int getCount() {
if (itemsLoaded) {
return (showOnlyNewEpisodes) ? unreadItems.size() : recentItems.size();
return episodes.size();
}
return 0;
}
@ -353,7 +408,7 @@ public class AllEpisodesFragment extends Fragment {
@Override
public FeedItem getItem(int position) {
if (itemsLoaded) {
return (showOnlyNewEpisodes) ? unreadItems.get(position) : recentItems.get(position);
return episodes.get(position);
}
return null;
}
@ -374,7 +429,17 @@ public class AllEpisodesFragment extends Fragment {
@Override
public boolean isInQueue(FeedItem item) {
if (itemsLoaded) {
return queueAccess.contains(item.getId());
return queuedItemsIds.contains(item.getId());
} else {
return false;
}
}
@Override
public boolean isNew(FeedItem item) {
if (itemsLoaded) {
// should actually never be called in NewEpisodesFragment, but better safe than sorry
return showOnlyNewEpisodes || newItemsIds.contains(item.getId());
} else {
return false;
}
@ -436,10 +501,19 @@ public class AllEpisodesFragment extends Fragment {
protected Object[] doInBackground(Void... params) {
Context context = activity.get();
if (context != null) {
return new Object[]{
DBReader.getUnreadItemsList(context),
DBReader.getRecentlyPublishedEpisodes(context, RECENT_EPISODES_LIMIT),
DBReader.getQueueIDList(context)};
if(showOnlyNewEpisodes) {
return new Object[] {
DBReader.getNewItemsList(context),
DBReader.getQueueIDList(context),
null // see ItemAccess.isNew
};
} else {
return new Object[]{
DBReader.getRecentlyPublishedEpisodes(context, RECENT_EPISODES_LIMIT),
DBReader.getQueueIDList(context),
DBReader.getNewItemIds(context)
};
}
} else {
return null;
}
@ -452,9 +526,9 @@ public class AllEpisodesFragment extends Fragment {
progLoading.setVisibility(View.GONE);
if (lists != null) {
unreadItems = (List<FeedItem>) lists[0];
recentItems = (List<FeedItem>) lists[1];
queueAccess = (LongList) lists[2];
episodes = (List<FeedItem>) lists[0];
queuedItemsIds = (LongList) lists[1];
newItemsIds = (LongList) lists[2];
itemsLoaded = true;
if (viewsCreated && activity.get() != null) {
onFragmentLoaded();

View File

@ -270,7 +270,7 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba
return;
}
popupMenu.getMenu().clear();
popupMenu.inflate(R.menu.feeditem_dialog);
popupMenu.inflate(R.menu.feeditem_options);
if (item.hasMedia()) {
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue);
} else {

View File

@ -9,20 +9,24 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ListFragment;
import android.support.v4.util.Pair;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.IconTextView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.joanzapata.android.iconify.Iconify;
@ -57,6 +61,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.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.greenrobot.event.EventBus;
@ -77,10 +82,13 @@ public class ItemlistFragment extends ListFragment {
public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
protected FeedItemlistAdapter adapter;
private ContextMenu contextMenu;
private long feedID;
private Feed feed;
private LongList queue;
private LongList queuedItemsIds;
private LongList newItemsIds;
private boolean itemsLoaded = false;
private boolean viewsCreated = false;
@ -92,6 +100,8 @@ public class ItemlistFragment extends ListFragment {
private boolean isUpdatingFeed;
private IconTextView txtvFailure;
private TextView txtvInformation;
/**
@ -261,9 +271,60 @@ public class ItemlistFragment extends ListFragment {
} else {
return true;
}
}
private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
if(contextMenu == null) {
return;
}
MenuItem item = contextMenu.findItem(id);
if (item != null) {
item.setVisible(visible);
}
}
};
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
// because of addHeaderView(), positions are increased by 1!
FeedItem item = itemAccess.getItem(adapterInfo.position-1);
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.feeditemlist_context, menu);
if (item != null) {
menu.setHeaderTitle(item.getTitle());
}
contextMenu = menu;
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queuedItemsIds);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
// because of addHeaderView(), positions are increased by 1!
FeedItem selectedItem = itemAccess.getItem(menuInfo.position-1);
if (selectedItem == null) {
Log.i(TAG, "Selected item at position " + menuInfo.position + " was null, ignoring selection");
return super.onContextItemSelected(item);
}
try {
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
// context menu doesn't contain download functionality
return true;
}
}
@Override
public void setListAdapter(ListAdapter adapter) {
// This workaround prevents the ListFragment from setting a list adapter when its state is restored.
@ -278,6 +339,8 @@ public class ItemlistFragment extends ListFragment {
super.onViewCreated(view, savedInstanceState);
((ActionBarActivity) getActivity()).getSupportActionBar().setTitle("");
registerForContextMenu(getListView());
viewsCreated = true;
if (itemsLoaded) {
onFragmentLoaded();
@ -309,7 +372,7 @@ public class ItemlistFragment extends ListFragment {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((EVENTS & arg) != 0) {
Log.d(TAG, "Received contentUpdate Intent.");
Log.d(TAG, "Received contentUpdate Intent. arg " + arg);
if ((EventDistributor.DOWNLOAD_QUEUED & arg) != 0) {
updateProgressBarVisibility();
} else {
@ -358,9 +421,18 @@ public class ItemlistFragment extends ListFragment {
}
private void refreshHeaderView() {
if(feed.hasLastUpdateFailed()) {
txtvFailure.setVisibility(View.VISIBLE);
} else {
txtvFailure.setVisibility(View.GONE);
}
if(feed.getItemFilter() != null) {
FeedItemFilter filter = feed.getItemFilter();
if(filter.getValues().length > 0) {
if(feed.hasLastUpdateFailed()) {
RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) txtvInformation.getLayoutParams();
p.addRule(RelativeLayout.BELOW, R.id.txtvFailure);
}
txtvInformation.setText("{fa-info-circle} " + this.getString(R.string.filtered_label));
Iconify.addIcons(txtvInformation);
txtvInformation.setVisibility(View.VISIBLE);
@ -368,6 +440,7 @@ public class ItemlistFragment extends ListFragment {
txtvInformation.setVisibility(View.GONE);
}
} else {
txtvInformation.setVisibility(View.GONE);
}
}
@ -407,6 +480,7 @@ public class ItemlistFragment extends ListFragment {
ImageView imgvCover = (ImageView) header.findViewById(R.id.imgvCover);
ImageButton butShowInfo = (ImageButton) header.findViewById(R.id.butShowInfo);
txtvInformation = (TextView) header.findViewById(R.id.txtvInformation);
txtvFailure = (IconTextView) header.findViewById(R.id.txtvFailure);
txtvTitle.setText(feed.getTitle());
txtvAuthor.setText(feed.getAuthor());
@ -437,6 +511,7 @@ public class ItemlistFragment extends ListFragment {
});
}
private void setupFooterView() {
if (getListView() == null || feed == null) {
Log.e(TAG, "Unable to setup listview: listView = null or feed = null");
@ -469,17 +544,22 @@ public class ItemlistFragment extends ListFragment {
@Override
public FeedItem getItem(int position) {
return (feed != null) ? feed.getItemAtIndex(true, position) : null;
return (feed != null) ? feed.getItemAtIndex(position) : null;
}
@Override
public int getCount() {
return (feed != null) ? feed.getNumOfItems(true) : 0;
return (feed != null) ? feed.getNumOfItems() : 0;
}
@Override
public boolean isInQueue(FeedItem item) {
return (queue != null) && queue.contains(item.getId());
return (queuedItemsIds != null) && queuedItemsIds.contains(item.getId());
}
@Override
public boolean isNew(FeedItem item) {
return (newItemsIds != null) && newItemsIds.contains(item.getId());
}
@Override
@ -512,9 +592,9 @@ public class ItemlistFragment extends ListFragment {
}
}
private class ItemLoader extends AsyncTask<Long, Void, Pair<Feed,LongList>> {
private class ItemLoader extends AsyncTask<Long, Void, Object[]> {
@Override
protected Pair<Feed,LongList> doInBackground(Long... params) {
protected Object[] doInBackground(Long... params) {
long feedID = params[0];
Context context = getActivity();
if (context != null) {
@ -523,19 +603,21 @@ public class ItemlistFragment extends ListFragment {
FeedItemFilter filter = feed.getItemFilter();
feed.setItems(filter.filter(context, feed.getItems()));
}
LongList queue = DBReader.getQueueIDList(context);
return Pair.create(feed, queue);
LongList queuedItemsIds = DBReader.getQueueIDList(context);
LongList newItemsIds = DBReader.getNewItemIds(context);
return new Object[] { feed, queuedItemsIds, newItemsIds };
} else {
return null;
}
}
@Override
protected void onPostExecute(Pair<Feed,LongList> res) {
protected void onPostExecute(Object[] res) {
super.onPostExecute(res);
if (res != null) {
feed = res.first;
queue = res.second;
feed = (Feed) res[0];
queuedItemsIds = (LongList) res[1];
newItemsIds = res[2] == null ? null : (LongList) res[2];
itemsLoaded = true;
if (viewsCreated) {
onFragmentLoaded();

View File

@ -225,6 +225,11 @@ public class PlaybackHistoryFragment extends ListFragment {
return (queue != null) ? queue.contains(item.getId()) : false;
}
@Override
public boolean isNew(FeedItem item) {
return false;
}
@Override
public int getItemDownloadProgressPercent(FeedItem item) {
if (downloaderList != null) {

View File

@ -21,6 +21,7 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.mobeta.android.dslv.DragSortListView;
@ -44,10 +45,13 @@ import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
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.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.QueueSorter;
import de.danoeh.antennapod.core.util.gui.FeedItemUndoToken;
import de.danoeh.antennapod.core.util.gui.UndoBarController;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.greenrobot.event.EventBus;
@ -67,6 +71,8 @@ public class QueueFragment extends Fragment {
private TextView txtvEmpty;
private ProgressBar progLoading;
private ContextMenu contextMenu;
private UndoBarController<FeedItemUndoToken> undoBarController;
private List<FeedItem> queue;
@ -292,6 +298,19 @@ public class QueueFragment extends Fragment {
}
private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
if(contextMenu == null) {
return;
}
MenuItem item = contextMenu.findItem(id);
if (item != null) {
item.setVisible(visible);
}
}
};
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
@ -305,8 +324,12 @@ public class QueueFragment extends Fragment {
menu.setHeaderTitle(item.getTitle());
}
menu.findItem(R.id.move_to_top_item).setEnabled(!queue.isEmpty() && queue.get(0) != item);
menu.findItem(R.id.move_to_bottom_item).setEnabled(!queue.isEmpty() && queue.get(queue.size() - 1) != item);
contextMenu = menu;
LongList queueIds = new LongList(queue.size());
for(FeedItem queueItem : queue) {
queueIds.add(queueItem.getId());
}
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queueIds);
}
@Override
@ -319,21 +342,16 @@ public class QueueFragment extends Fragment {
return super.onContextItemSelected(item);
}
switch (item.getItemId()) {
case R.id.move_to_top_item:
DBWriter.moveQueueItemToTop(getActivity(), selectedItem.getId(), true);
return true;
case R.id.move_to_bottom_item:
DBWriter.moveQueueItemToBottom(getActivity(), selectedItem.getId(), true);
return true;
case R.id.remove_from_queue_item:
DBWriter.removeQueueItem(getActivity(), selectedItem, false);
return true;
default:
return super.onContextItemSelected(item);
try {
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
return true;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
@ -384,7 +402,6 @@ public class QueueFragment extends Fragment {
Log.d(TAG, "remove(" + which + ")");
stopItemLoader();
FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
DBWriter.markItemRead(getActivity(), item.getId(), true);
DBWriter.removeQueueItem(getActivity(), item, true);
}
});
@ -399,7 +416,6 @@ public class QueueFragment extends Fragment {
if (token != null) {
long itemId = token.getFeedItemId();
int position = token.getPosition();
DBWriter.markItemRead(context, itemId, false);
DBWriter.addQueueItemAt(context, itemId, position, false);
}
}
@ -418,7 +434,6 @@ public class QueueFragment extends Fragment {
});
registerForContextMenu(listView);
if (!itemsLoaded) {

View File

@ -3,14 +3,15 @@ package de.danoeh.antennapod.menuhandler;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
@ -55,18 +56,12 @@ public class FeedItemMenuHandler {
* @param queueAccess Used for testing if the queue contains the selected item
* @return Returns true if selectedItem is not null.
*/
public static boolean onPrepareMenu(MenuInterface mi,
FeedItem selectedItem, boolean showExtendedMenu, LongList queueAccess) {
public static boolean onPrepareMenu(MenuInterface mi, FeedItem selectedItem,
boolean showExtendedMenu, LongList queueAccess) {
if (selectedItem == null) {
return false;
}
DownloadRequester requester = DownloadRequester.getInstance();
boolean hasMedia = selectedItem.getMedia() != null;
boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded();
boolean downloading = hasMedia
&& requester.isDownloadingFile(selectedItem.getMedia());
boolean notLoadedAndNotLoading = hasMedia && (!downloaded)
&& (!downloading);
boolean isPlaying = hasMedia
&& selectedItem.getState() == FeedItem.State.PLAYING;
@ -75,21 +70,14 @@ public class FeedItemMenuHandler {
if (!isPlaying) {
mi.setItemVisibility(R.id.skip_episode_item, false);
}
if (!downloaded || isPlaying) {
mi.setItemVisibility(R.id.play_item, false);
mi.setItemVisibility(R.id.remove_item, false);
}
if (!notLoadedAndNotLoading) {
mi.setItemVisibility(R.id.download_item, false);
}
if (!(notLoadedAndNotLoading | downloading) | isPlaying) {
mi.setItemVisibility(R.id.stream_item, false);
}
if (!downloading) {
mi.setItemVisibility(R.id.cancel_download_item, false);
}
boolean isInQueue = queueAccess.contains(selectedItem.getId());
if(queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId()) {
mi.setItemVisibility(R.id.move_to_top_item, false);
}
if(queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == selectedItem.getId()) {
mi.setItemVisibility(R.id.move_to_bottom_item, false);
}
if (!isInQueue || isPlaying) {
mi.setItemVisibility(R.id.remove_from_queue_item, false);
}
@ -100,12 +88,24 @@ public class FeedItemMenuHandler {
mi.setItemVisibility(R.id.share_link_item, false);
}
if (!BuildConfig.DEBUG
|| !(state == FeedItem.State.IN_PROGRESS || state == FeedItem.State.READ)) {
if (!(state == FeedItem.State.UNREAD || state == FeedItem.State.IN_PROGRESS)) {
mi.setItemVisibility(R.id.mark_read_item, false);
}
if (!(state == FeedItem.State.IN_PROGRESS || state == FeedItem.State.READ)) {
mi.setItemVisibility(R.id.mark_unread_item, false);
}
if (!(state == FeedItem.State.NEW || state == FeedItem.State.IN_PROGRESS)) {
mi.setItemVisibility(R.id.mark_read_item, false);
if(selectedItem.getMedia() == null || selectedItem.getMedia().getPosition() == 0) {
mi.setItemVisibility(R.id.reset_position, false);
}
if(false == UserPreferences.isEnableAutodownload()) {
mi.setItemVisibility(R.id.activate_auto_download, false);
mi.setItemVisibility(R.id.deactivate_auto_download, false);
} else if(selectedItem.getAutoDownload()) {
mi.setItemVisibility(R.id.activate_auto_download, false);
} else {
mi.setItemVisibility(R.id.deactivate_auto_download, false);
}
if (!showExtendedMenu || selectedItem.getLink() == null) {
@ -142,24 +142,14 @@ public class FeedItemMenuHandler {
DownloadRequester requester = DownloadRequester.getInstance();
switch (menuItemId) {
case R.id.skip_episode_item:
context.sendBroadcast(new Intent(
PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
break;
case R.id.download_item:
DBTasks.downloadFeedItems(context, selectedItem);
break;
case R.id.play_item:
DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
false);
context.sendBroadcast(new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
break;
case R.id.remove_item:
DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId());
break;
case R.id.cancel_download_item:
requester.cancelDownload(context, selectedItem.getMedia());
break;
case R.id.mark_read_item:
DBWriter.markItemRead(context, selectedItem, true, true);
selectedItem.setRead(true);
DBWriter.markItemRead(context, selectedItem, true, false);
if(GpodnetPreferences.loggedIn()) {
FeedMedia media = selectedItem.getMedia();
GpodnetEpisodeAction actionPlay = new GpodnetEpisodeAction.Builder(selectedItem, Action.PLAY)
@ -173,7 +163,8 @@ public class FeedItemMenuHandler {
}
break;
case R.id.mark_unread_item:
DBWriter.markItemRead(context, selectedItem, false, true);
selectedItem.setRead(false);
DBWriter.markItemRead(context, selectedItem, false, false);
if(GpodnetPreferences.loggedIn()) {
GpodnetEpisodeAction actionNew = new GpodnetEpisodeAction.Builder(selectedItem, Action.NEW)
.currentDeviceId()
@ -182,15 +173,28 @@ public class FeedItemMenuHandler {
GpodnetPreferences.enqueueEpisodeAction(actionNew);
}
break;
case R.id.move_to_top_item:
DBWriter.moveQueueItemToTop(context, selectedItem.getId(), true);
return true;
case R.id.move_to_bottom_item:
DBWriter.moveQueueItemToBottom(context, selectedItem.getId(), true);
case R.id.add_to_queue_item:
DBWriter.addQueueItem(context, selectedItem.getId());
break;
case R.id.remove_from_queue_item:
DBWriter.removeQueueItem(context, selectedItem, true);
break;
case R.id.stream_item:
DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
true);
case R.id.reset_position:
selectedItem.getMedia().setPosition(0);
DBWriter.markItemRead(context, selectedItem, false, true);
break;
case R.id.activate_auto_download:
selectedItem.setAutoDownload(true);
DBWriter.setFeedItemAutoDownload(context, selectedItem, true);
break;
case R.id.deactivate_auto_download:
selectedItem.setAutoDownload(false);
DBWriter.setFeedItemAutoDownload(context, selectedItem, false);
break;
case R.id.visit_website_item:
Uri uri = Uri.parse(selectedItem.getLink());
@ -203,6 +207,7 @@ public class FeedItemMenuHandler {
ShareUtils.shareFeedItemLink(context, selectedItem);
break;
default:
Log.d(TAG, "Unknown menuItemId: " + menuItemId);
return false;
}
// Refresh menu state

View File

@ -15,7 +15,6 @@ import java.util.Arrays;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.storage.DBTasks;
@ -39,10 +38,8 @@ public class FeedMenuHandler {
return true;
}
if (BuildConfig.DEBUG)
Log.d(TAG, "Preparing options menu");
menu.findItem(R.id.mark_all_read_item).setVisible(
selectedFeed.hasNewItems(true));
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
@ -132,7 +129,7 @@ public class FeedMenuHandler {
builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
feed.setFeedItemsFilter(hidden.toArray(new String[hidden.size()]));
feed.setHiddenItemProperties(hidden.toArray(new String[hidden.size()]));
DBWriter.setFeedItemsFilter(context, feed.getId(), hidden);
}
});

View File

@ -78,6 +78,21 @@
tools:text="Podcast author"
tools:background="@android:color/holo_green_dark" />
<IconTextView
android:id="@+id/txtvFailure"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/imgvBackground"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:background="@color/download_failed_red"
android:gravity="center"
android:textColor="@color/white"
android:visibility="gone"
android:text="@string/refresh_failed_msg"
tools:text="(!) Last refresh failed"
/>
<TextView
android:id="@+id/txtvInformation"
android:layout_width="match_parent"

View File

@ -22,8 +22,10 @@
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_margin="16dp"
tools:text="Status unread"
android:layout_marginTop="16dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
tools:text="NEW"
tools:background="@android:color/white" />
<TextView
@ -36,34 +38,9 @@
android:layout_marginBottom="8dp"
android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
android:layout_toLeftOf="@id/statusUnread"
tools:text="Feed item name"
tools:text="Episode title"
tools:background="@android:color/holo_green_dark" />
<ImageView
android:id="@+id/imgvInPlaylist"
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_alignParentRight="true"
android:layout_below="@id/txtvItemname"
android:layout_marginRight="4dp"
android:contentDescription="@string/in_queue_label"
android:src="?attr/stat_playlist"
android:visibility="visible"
tools:src="@drawable/ic_list_white_24dp"
tools:background="@android:color/holo_red_light" />
<ImageView
android:id="@+id/imgvType"
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_below="@id/txtvItemname"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@+id/imgvInPlaylist"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_hearing_white_18dp"
tools:background="@android:color/holo_red_light" />
<TextView
android:id="@+id/txtvLenSize"
style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
@ -74,18 +51,29 @@
tools:text="00:42:23"
tools:background="@android:color/holo_green_dark" />
<ProgressBar
android:id="@+id/pbar_episode_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
<ImageView
android:id="@+id/imgvInPlaylist"
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_alignParentRight="true"
android:layout_below="@id/txtvItemname"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toLeftOf="@id/imgvType"
android:layout_toRightOf="@id/txtvLenSize"
tools:background="@android:color/holo_blue_light" />
android:layout_marginRight="8dp"
android:contentDescription="@string/in_queue_label"
android:src="?attr/stat_playlist"
android:visibility="visible"
tools:src="@drawable/ic_list_white_24dp"
tools:background="@android:color/holo_red_light" />
<ImageView
android:id="@+id/imgvType"
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_below="@id/txtvItemname"
android:layout_marginRight="8dp"
android:layout_toLeftOf="@id/imgvInPlaylist"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_hearing_white_18dp"
tools:background="@android:color/holo_red_light" />
<TextView
android:id="@+id/txtvPublished"
@ -93,10 +81,30 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/txtvItemname"
android:layout_marginRight="4dp"
android:layout_marginRight="8dp"
android:layout_toLeftOf="@id/imgvType"
tools:text="Jan 23"
tools:background="@android:color/holo_green_dark" />
<ProgressBar
android:id="@+id/pbar_episode_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_toLeftOf="@id/txtvPublished"
android:layout_toRightOf="@id/txtvLenSize"
android:layout_alignTop="@id/txtvPublished"
android:layout_alignBottom="@id/txtvPublished"
tools:background="@android:color/holo_blue_light"
android:max="100"
android:progress="42"
android:indeterminate="false"
/>
</RelativeLayout>
<include layout="@layout/vertical_list_divider"/>

View File

@ -7,7 +7,6 @@
android:layout_height="@dimen/listitem_iconwithtext_height"
tools:background="@android:color/darker_gray">
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
@ -24,7 +23,6 @@
tools:src="@drawable/ic_stat_antenna_default"
tools:background="@android:color/holo_green_dark"/>
<TextView
android:id="@+id/txtvTitle"
android:lines="1"
@ -39,6 +37,34 @@
android:layout_marginRight="@dimen/listitem_icon_rightpadding"
android:layout_toRightOf="@id/imgvCover"
tools:text="Navigation feed item title"
tools:background="@android:color/holo_green_dark"
/>
tools:background="@android:color/holo_green_dark"/>
<TextView
android:id="@+id/txtvCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_navdrawer"
android:layout_marginLeft="8dp"
android:layout_marginRight="@dimen/listitem_icon_rightpadding"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
tools:text="23"
tools:background="@android:color/holo_green_dark"/>
<IconTextView
android:id="@+id/itxtvFailure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/txtvCount"
android:lines="1"
android:text="{fa-exclamation-circle}"
android:textColor="@color/download_failed_red"
android:textSize="@dimen/text_size_navdrawer"
android:layout_marginLeft="8dp"
android:layout_centerVertical="true"
tools:text="!"
tools:background="@android:color/holo_green_dark"/>
</RelativeLayout>

View File

@ -37,7 +37,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true" />
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"/>
<TextView
android:id="@+id/txtvTitle"
@ -60,38 +61,26 @@
android:layout_marginTop="16dp"
tools:background="@android:color/holo_red_light" >
<TextView
android:id="@+id/txtvDuration"
style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
tools:text="00:42:23"
tools:background="@android:color/holo_blue_dark" />
<ImageView
android:id="@id/imgvInPlaylist"
android:id="@+id/imgvInPlaylist"
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_alignParentRight="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="4dp"
android:contentDescription="@string/in_queue_label"
android:src="?attr/stat_playlist"
tools:src="@drawable/ic_list_grey600_24dp"
tools:background="@android:color/black" />
<ProgressBar
android:id="@+id/pbar_download_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginRight="8dp"
android:layout_toLeftOf="@id/imgvInPlaylist"
android:max="100" />
<TextView
android:id="@+id/txtvDuration"
style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/imgvInPlaylist"
tools:text="00:42:23"
tools:background="@android:color/holo_blue_dark" />
<TextView
android:id="@+id/txtvPublished"
style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
@ -103,6 +92,17 @@
tools:text="Jan 23"
tools:background="@android:color/holo_green_dark" />
<ProgressBar
android:id="@+id/pbar_download_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_toLeftOf="@id/txtvPublished"
android:layout_toRightOf="@id/txtvDuration"
android:max="100" />
</RelativeLayout>
</RelativeLayout>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/skip_episode_item"
android:menuCategory="container"
android:title="@string/skip_episode_label" />
<item
android:id="@+id/mark_read_item"
android:menuCategory="container"
android:title="@string/mark_read_label" />
<item
android:id="@+id/mark_unread_item"
android:menuCategory="container"
android:title="@string/mark_unread_label" />
<item
android:id="@+id/add_to_queue_item"
android:menuCategory="container"
android:title="@string/add_to_queue_label" />
<item
android:id="@+id/remove_from_queue_item"
android:menuCategory="container"
android:title="@string/remove_from_queue_label" />
<item
android:id="@+id/reset_position"
android:menuCategory="container"
android:title="@string/reset_position" />
<item
android:id="@+id/activate_auto_download"
android:menuCategory="container"
android:title="@string/activate_auto_download" />
<item
android:id="@+id/deactivate_auto_download"
android:menuCategory="container"
android:title="@string/deactivate_auto_download" />
<item
android:id="@+id/share_link_item"
android:menuCategory="container"
android:title="@string/share_link_label" />
<item
android:id="@+id/visit_website_item"
android:menuCategory="container"
android:title="@string/visit_website_label" />
<item
android:id="@+id/support_item"
android:menuCategory="container"
android:title="@string/support_label" />
</menu>

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/download_item"
android:icon="?attr/av_download"
custom:showAsAction="collapseActionView"
android:title="@string/download_label">
</item>
<item
android:id="@+id/stream_item"
android:icon="?attr/action_stream"
custom:showAsAction="collapseActionView"
android:title="@string/stream_label">
</item>
<item
android:id="@+id/play_item"
android:icon="?attr/av_play"
custom:showAsAction="collapseActionView"
android:title="@string/play_label">
</item>
<item
android:id="@+id/remove_item"
android:icon="?attr/content_discard"
custom:showAsAction="collapseActionView"
android:title="@string/remove_label">
</item>
<item
android:id="@id/skip_episode_item"
android:title="@string/skip_episode_label"
custom:showAsAction="collapseActionView">
</item>
<item
android:id="@+id/cancel_download_item"
android:icon="?attr/navigation_cancel"
custom:showAsAction="ifRoom|collapseActionView"
android:title="@string/cancel_download_label">
</item>
<item
android:id="@+id/mark_read_item"
custom:showAsAction="collapseActionView"
android:title="@string/mark_read_label">
</item>
<item
android:id="@+id/mark_unread_item"
custom:showAsAction="collapseActionView"
android:title="@string/mark_unread_label">
</item>
<item
android:id="@+id/add_to_queue_item"
custom:showAsAction="collapseActionView"
android:title="@string/add_to_queue_label">
</item>
<item
android:id="@+id/remove_from_queue_item"
custom:showAsAction="collapseActionView"
android:title="@string/remove_from_queue_label">
</item>
<item
android:id="@+id/share_link_item"
custom:showAsAction="collapseActionView"
android:title="@string/share_link_label">
</item>
<item
android:id="@+id/visit_website_item"
android:icon="?attr/location_web_site"
custom:showAsAction="collapseActionView"
android:title="@string/visit_website_label">
</item>
<item
android:id="@+id/support_item"
custom:showAsAction="collapseActionView"
android:title="@string/support_label">
</item>
</menu>

View File

@ -18,6 +18,7 @@
custom:showAsAction="collapseActionView"
android:title="@string/mark_unread_label">
</item>
<item
android:id="@+id/add_to_queue_item"
custom:showAsAction="collapseActionView"
@ -28,6 +29,24 @@
custom:showAsAction="collapseActionView"
android:title="@string/remove_from_queue_label">
</item>
<item
android:id="@+id/reset_position"
custom:showAsAction="collapseActionView"
android:title="@string/reset_position">
</item>
<item
android:id="@+id/activate_auto_download"
custom:showAsAction="collapseActionView"
android:title="@string/activate_auto_download">
</item>
<item
android:id="@+id/deactivate_auto_download"
custom:showAsAction="collapseActionView"
android:title="@string/deactivate_auto_download">
</item>
<item
android:id="@+id/share_link_item"
custom:showAsAction="collapseActionView"

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/skip_episode_item"
android:menuCategory="container"
android:title="@string/skip_episode_label" />
<item
android:id="@+id/mark_read_item"
android:menuCategory="container"
android:title="@string/mark_read_label" />
<item
android:id="@+id/mark_unread_item"
android:menuCategory="container"
android:title="@string/mark_unread_label" />
<item
android:id="@+id/add_to_queue_item"
android:menuCategory="container"
android:title="@string/add_to_queue_label" />
<item
android:id="@+id/remove_from_queue_item"
android:menuCategory="container"
android:title="@string/remove_from_queue_label" />
<item
android:id="@+id/reset_position"
android:menuCategory="container"
android:title="@string/reset_position" />
<item
android:id="@+id/activate_auto_download"
android:menuCategory="container"
android:title="@string/activate_auto_download" />
<item
android:id="@+id/deactivate_auto_download"
android:menuCategory="container"
android:title="@string/deactivate_auto_download" />
<item
android:id="@+id/share_link_item"
android:menuCategory="container"
android:title="@string/share_link_label" />
<item
android:id="@+id/visit_website_item"
android:menuCategory="container"
android:title="@string/visit_website_label" />
<item
android:id="@+id/support_item"
android:menuCategory="container"
android:title="@string/support_label" />
</menu>

View File

@ -7,14 +7,54 @@
android:menuCategory="container"
android:title="@string/move_to_top_label" />
<item
android:id="@+id/move_to_bottom_item"
android:menuCategory="container"
android:title="@string/move_to_bottom_label" />
<item
android:id="@+id/mark_read_item"
android:menuCategory="container"
android:title="@string/mark_read_label" />
<item
android:id="@+id/mark_unread_item"
android:menuCategory="container"
android:title="@string/mark_unread_label" />
<item
android:id="@+id/remove_from_queue_item"
android:menuCategory="container"
android:title="@string/remove_from_queue_label" />
<item
android:id="@+id/move_to_bottom_item"
android:id="@+id/reset_position"
android:menuCategory="container"
android:title="@string/move_to_bottom_label" />
android:title="@string/reset_position" />
<item
android:id="@+id/activate_auto_download"
android:menuCategory="container"
android:title="@string/activate_auto_download" />
<item
android:id="@+id/deactivate_auto_download"
android:menuCategory="container"
android:title="@string/deactivate_auto_download" />
<item
android:id="@+id/share_link_item"
android:menuCategory="container"
android:title="@string/share_link_label" />
<item
android:id="@+id/visit_website_item"
android:menuCategory="container"
android:title="@string/visit_website_label" />
<item
android:id="@+id/support_item"
android:menuCategory="container"
android:title="@string/support_label" />
</menu>

View File

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.2'
classpath 'com.android.tools.build:gradle:1.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -19,5 +19,5 @@ allprojects {
}
task wrapper(type: Wrapper) {
gradleVersion = '2.2.1'
gradleVersion = '2.4'
}

View File

@ -9,6 +9,8 @@ android {
targetSdkVersion 21
versionCode 1
versionName "1.0"
testApplicationId "de.danoeh.antennapod.core.tests"
testInstrumentationRunner "de.danoeh.antennapod.core.tests.AntennaPodTestRunner"
}
buildTypes {
release {

View File

@ -1,13 +0,0 @@
package de.danoeh.antennapod.core.test;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@ -0,0 +1,15 @@
package de.danoeh.antennapod.core.tests;
import android.test.InstrumentationTestRunner;
import android.test.suitebuilder.TestSuiteBuilder;
import junit.framework.TestSuite;
public class AntennaPodTestRunner extends InstrumentationTestRunner {
@Override
public TestSuite getAllTests() {
return new TestSuiteBuilder(AntennaPodTestRunner.class)
.includeAllPackagesUnderHere()
.build();
}
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util;
package de.danoeh.antennapod.core.tests.util;
import android.test.AndroidTestCase;
@ -7,6 +7,8 @@ import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import de.danoeh.antennapod.core.util.DateUtils;
public class DateUtilsTest extends AndroidTestCase {
public void testParseDateWithMicroseconds() throws Exception {

View File

@ -0,0 +1,61 @@
package de.danoeh.antennapod.core.tests.util;
import android.test.AndroidTestCase;
import de.danoeh.antennapod.core.util.LongIntMap;
public class LongLongMapTest extends AndroidTestCase {
public void testEmptyMap() {
LongIntMap map = new LongIntMap();
assertEquals(0, map.size());
assertEquals("LongLongMap{}", map.toString());
assertEquals(0, map.get(42));
assertEquals(-1, map.get(42, -1));
assertEquals(false, map.delete(42));
assertEquals(-1, map.indexOfKey(42));
assertEquals(-1, map.indexOfValue(42));
assertEquals(1, map.hashCode());
}
public void testSingleElement() {
LongIntMap map = new LongIntMap();
map.put(17, 42);
assertEquals(1, map.size());
assertEquals("LongLongMap{17=42}", map.toString());
assertEquals(42, map.get(17));
assertEquals(42, map.get(17, -1));
assertEquals(0, map.indexOfKey(17));
assertEquals(0, map.indexOfValue(42));
assertEquals(true, map.delete(17));
}
public void testAddAndDelete() {
LongIntMap map = new LongIntMap();
for(int i=0; i < 100; i++) {
map.put(i * 17, i * 42);
}
assertEquals(100, map.size());
assertEquals(0, map.get(0));
assertEquals(42, map.get(17));
assertEquals(42, map.get(17, -1));
assertEquals(1, map.indexOfKey(17));
assertEquals(1, map.indexOfValue(42));
for(int i=0; i < 100; i++) {
assertEquals(true, map.delete(i * 17));
}
}
public void testOverwrite() {
LongIntMap map = new LongIntMap();
map.put(17, 42);
assertEquals(1, map.size());
assertEquals("LongLongMap{17=42}", map.toString());
assertEquals(42, map.get(17));
map.put(17, 23);
assertEquals(1, map.size());
assertEquals("LongLongMap{17=23}", map.toString());
assertEquals(23, map.get(17));
}
}

View File

@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.feed;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
@ -79,6 +80,8 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
*/
private String nextPageLink;
private boolean lastUpdateFailed;
/**
* Contains property strings. If such a property applies to a feed item, it is not shown in the feed list
*/
@ -90,7 +93,7 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
String downloadUrl, boolean downloaded, FlattrStatus status, boolean paged, String nextPageLink,
String filter) {
String filter, boolean lastUpdateFailed) {
super(fileUrl, downloadUrl, downloaded);
this.id = id;
this.title = title;
@ -110,13 +113,13 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
this.flattrStatus = status;
this.paged = paged;
this.nextPageLink = nextPageLink;
this.items = new ArrayList<FeedItem>();
if(filter != null) {
this.itemfilter = new FeedItemFilter(filter);
} else {
this.itemfilter = new FeedItemFilter(new String[0]);
}
items = new ArrayList<FeedItem>();
this.lastUpdateFailed = lastUpdateFailed;
}
/**
@ -126,7 +129,7 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
String downloadUrl, boolean downloaded) {
this(id, lastUpdate, title, link, description, paymentLink, author, language, type, feedIdentifier, image,
fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null, null);
fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null, null, false);
}
/**
@ -134,7 +137,6 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
*/
public Feed() {
super();
items = new ArrayList<FeedItem>();
lastUpdate = new Date();
this.flattrStatus = new FlattrStatus();
}
@ -172,13 +174,10 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
/**
* Returns true if at least one item in the itemlist is unread.
*
* @param enableEpisodeFilter true if this method should only count items with episodes if
* the 'display only episodes' - preference is set to true by the
* user.
*/
public boolean hasNewItems(boolean enableEpisodeFilter) {
public boolean hasNewItems() {
for (FeedItem item : items) {
if (item.getState() == FeedItem.State.NEW) {
if (item.getState() == FeedItem.State.UNREAD) {
if (item.getMedia() != null) {
return true;
}
@ -190,21 +189,16 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
/**
* Returns the number of FeedItems.
*
* @param enableEpisodeFilter true if this method should only count items with episodes if
* the 'display only episodes' - preference is set to true by the
* user.
*/
public int getNumOfItems(boolean enableEpisodeFilter) {
public int getNumOfItems() {
return items.size();
}
/**
* Returns the item at the specified index.
*
* @param enableEpisodeFilter true if this method should ignore items without episdodes if
* the episodes filter has been enabled by the user.
*/
public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) {
public FeedItem getItemAtIndex(int position) {
return items.get(position);
}
@ -483,14 +477,23 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
this.nextPageLink = nextPageLink;
}
@Nullable
public FeedItemFilter getItemFilter() {
return itemfilter;
}
public void setFeedItemsFilter(String[] filter) {
if(filter != null) {
this.itemfilter = new FeedItemFilter(filter);
public void setHiddenItemProperties(String[] properties) {
if (properties != null) {
this.itemfilter = new FeedItemFilter(properties);
}
}
public boolean hasLastUpdateFailed() {
return this.lastUpdateFailed;
}
public void setLastUpdateFailed(boolean lastUpdateFailed) {
this.lastUpdateFailed = lastUpdateFailed;
}
}

View File

@ -239,7 +239,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
}
public boolean isRead() {
return read || isInProgress();
return read;
}
public void setRead(boolean read) {
@ -330,7 +330,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
}
public enum State {
NEW, IN_PROGRESS, READ, PLAYING
UNREAD, IN_PROGRESS, READ, PLAYING
}
public State getState() {
@ -342,7 +342,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
return State.IN_PROGRESS;
}
}
return (isRead() ? State.READ : State.NEW);
return (isRead() ? State.READ : State.UNREAD);
}
public long getFeedId() {

View File

@ -11,7 +11,7 @@ import de.danoeh.antennapod.core.storage.DBReader;
public class FeedItemFilter {
private final String[] filter;
private final String[] properties;
private boolean hideUnplayed = false;
private boolean hidePaused = false;
@ -21,15 +21,15 @@ public class FeedItemFilter {
private boolean hideDownloaded = false;
private boolean hideNotDownloaded = false;
public FeedItemFilter(String filter) {
this(StringUtils.split(filter, ','));
public FeedItemFilter(String properties) {
this(StringUtils.split(properties, ','));
}
public FeedItemFilter(String[] filter) {
this.filter = filter;
for(String f : filter) {
public FeedItemFilter(String[] properties) {
this.properties = properties;
for(String property : properties) {
// see R.arrays.feed_filter_values
switch(f) {
switch(property) {
case "unplayed":
hideUnplayed = true;
break;
@ -56,7 +56,7 @@ public class FeedItemFilter {
}
public List<FeedItem> filter(Context context, List<FeedItem> items) {
if(filter.length == 0) {
if(properties.length == 0) {
return items;
}
List<FeedItem> result = new ArrayList<FeedItem>();
@ -76,7 +76,7 @@ public class FeedItemFilter {
}
public String[] getValues() {
return filter.clone();
return properties.clone();
}
}

View File

@ -1060,9 +1060,11 @@ public class DownloadService extends Service {
@Override
public void run() {
if (request.isDeleteOnFailure()) {
if(request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
DBWriter.setFeedLastUpdateFailed(DownloadService.this, request.getFeedfileId(), true);
} else if (request.isDeleteOnFailure()) {
Log.d(TAG, "Ignoring failed download, deleteOnFailure=true");
} else {
} else {
File dest = new File(request.getDestination());
if (dest.exists() && request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
Log.d(TAG, "File has been partially downloaded. Writing file url");

View File

@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
@ -23,6 +24,7 @@ import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
@ -176,8 +178,7 @@ public final class DBReader {
*/
public static List<FeedItem> getFeedItemList(Context context,
final Feed feed) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
@ -317,7 +318,8 @@ public final class DBReader {
new FlattrStatus(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_FLATTR_STATUS)),
cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_IS_PAGED) > 0,
cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_NEXT_PAGE_LINK),
cursor.getString(cursor.getColumnIndex(PodDBAdapter.KEY_HIDE))
cursor.getString(cursor.getColumnIndex(PodDBAdapter.KEY_HIDE)),
cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED)) > 0
);
if (image != null) {
@ -488,6 +490,29 @@ public final class DBReader {
return items;
}
/**
* Loads a list of FeedItems that are considered new.
*
* @param context A context that is used for opening a database connection.
* @return A list of FeedItems that are considered new.
*/
public static List<FeedItem> getNewItemsList(Context context) {
Log.d(TAG, "getNewItemsList()");
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor itemlistCursor = adapter.getNewItemsCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
loadFeedDataOfFeedItemlist(context, items);
adapter.close();
return items;
}
/**
* Loads the IDs of the FeedItems whose 'read'-attribute is set to false.
*
@ -495,15 +520,16 @@ public final class DBReader {
* @return A list of IDs of the FeedItems whose 'read'-attribute is set to false. This method should be preferred
* over {@link #getUnreadItemsList(android.content.Context)} if the FeedItems in the UnreadItems list are not used.
*/
public static long[] getUnreadItemIds(Context context) {
public static LongList getNewItemIds(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor cursor = adapter.getUnreadItemIdsCursor();
long[] itemIds = new long[cursor.getCount()];
Cursor cursor = adapter.getNewItemIdsCursor();
LongList itemIds = new LongList(cursor.getCount());
int i = 0;
if (cursor.moveToFirst()) {
do {
itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
long id = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
itemIds.add(id);
i++;
} while (cursor.moveToNext());
}
@ -956,10 +982,24 @@ public final class DBReader {
* @param context A context that is used for opening a database connection.
* @return The number of unread items.
*/
public static int getNumberOfUnreadItems(final Context context) {
public static int getNumberOfNewItems(final Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
final int result = adapter.getNumberOfUnreadItems();
final int result = adapter.getNumberOfNewItems();
adapter.close();
return result;
}
/**
* Returns a map containing the number of unread items per feed
*
* @param context A context that is used for opening a database connection.
* @return The number of unread items per feed.
*/
public static LongIntMap getNumberOfUnreadFeedItems(final Context context, long... feedIds) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
final LongIntMap result = adapter.getNumberOfUnreadFeedItems(feedIds);
adapter.close();
return result;
}
@ -1088,9 +1128,31 @@ public final class DBReader {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<Feed> feeds = getFeedList(adapter);
long[] feedIds = new long[feeds.size()];
for(int i=0; i < feeds.size(); i++) {
feedIds[i] = feeds.get(i).getId();
}
final LongIntMap numUnreadFeedItems = adapter.getNumberOfUnreadFeedItems(feedIds);
Collections.sort(feeds, new Comparator<Feed>() {
@Override
public int compare(Feed lhs, Feed rhs) {
long numUnreadLhs = numUnreadFeedItems.get(lhs.getId());
Log.d(TAG, "feed with id " + lhs.getId() + " has " + numUnreadLhs + " unread items");
long numUnreadRhs = numUnreadFeedItems.get(rhs.getId());
Log.d(TAG, "feed with id " + rhs.getId() + " has " + numUnreadRhs + " unread items");
if(numUnreadLhs > numUnreadRhs) {
// reverse natural order: podcast with most unplayed episodes first
return -1;
} else if(numUnreadLhs == numUnreadRhs) {
return lhs.getTitle().compareTo(rhs.getTitle());
} else {
return 1;
}
}
});
int queueSize = adapter.getQueueSize();
int numUnreadItems = adapter.getNumberOfUnreadItems();
NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems);
int numNewItems = adapter.getNumberOfNewItems();
NavDrawerData result = new NavDrawerData(feeds, queueSize, numNewItems, numUnreadFeedItems);
adapter.close();
return result;
}
@ -1098,12 +1160,15 @@ public final class DBReader {
public static class NavDrawerData {
public List<Feed> feeds;
public int queueSize;
public int numUnreadItems;
public int numNewItems;
public LongIntMap numUnreadFeedItems;
public NavDrawerData(List<Feed> feeds, int queueSize, int numUnreadItems) {
public NavDrawerData(List<Feed> feeds, int queueSize, int numNewItems,
LongIntMap numUnreadFeedItems) {
this.feeds = feeds;
this.queueSize = queueSize;
this.numUnreadItems = numUnreadItems;
this.numNewItems = numNewItems;
this.numUnreadFeedItems = numUnreadFeedItems;
}
}
}

View File

@ -20,7 +20,6 @@ import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.asynctask.FlattrStatusFetcher;
@ -221,8 +220,7 @@ public final class DBTasks {
* @param context Used for DB access.
*/
public static void refreshExpiredFeeds(final Context context) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Refreshing expired feeds");
Log.d(TAG, "Refreshing expired feeds");
new Thread() {
public void run() {
@ -304,6 +302,7 @@ public final class DBTasks {
*/
public static void refreshFeed(Context context, Feed feed)
throws DownloadRequestException {
Log.d(TAG, "id " + feed.getId());
refreshFeed(context, feed, false);
}
@ -457,14 +456,6 @@ public final class DBTasks {
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().getDefaultCleanupParameter(context));
}
/**
* Adds all FeedItem objects whose 'read'-attribute is false to the queue in a separate thread.
*/
public static void enqueueAllNewItems(final Context context) {
long[] unreadItems = DBReader.getUnreadItemIds(context);
DBWriter.addQueueItem(context, unreadItems);
}
/**
* Returns the successor of a FeedItem in the queue.
*
@ -620,6 +611,7 @@ public final class DBTasks {
// update attributes
savedFeed.setLastUpdate(newFeed.getLastUpdate());
savedFeed.setType(newFeed.getType());
savedFeed.setLastUpdateFailed(false);
updatedFeedsList.add(savedFeed);
resultFeeds[feedIdx] = savedFeed;

View File

@ -354,30 +354,16 @@ public class DBWriter {
FeedItem item = null;
if (queue != null) {
boolean queueModified = false;
boolean unreadItemsModified = false;
if (!itemListContains(queue, itemId)) {
item = DBReader.getFeedItem(context, itemId);
if (item != null) {
queue.add(index, item);
queueModified = true;
if (!item.isRead()) {
item.setRead(true);
unreadItemsModified = true;
}
adapter.setQueue(queue);
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index));
}
}
if (queueModified) {
adapter.setQueue(queue);
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index));
}
if (unreadItemsModified && item != null) {
adapter.setSingleFeedItem(item);
EventDistributor.getInstance()
.sendUnreadItemsUpdateBroadcast();
}
}
adapter.close();
if (performAutoDownload) {
DBTasks.autodownloadUndownloadedItems(context);
@ -422,16 +408,11 @@ public class DBWriter {
if(addToFront){
queue.add(0, item);
}else{
} else {
queue.add(item);
}
queueModified = true;
if (!item.isRead()) {
item.setRead(true);
itemsToSave.add(item);
unreadItemsModified = true;
}
}
}
}
@ -439,11 +420,6 @@ public class DBWriter {
adapter.setQueue(queue);
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED_ITEMS, queue));
}
if (unreadItemsModified) {
adapter.setFeedItemlist(itemsToSave);
EventDistributor.getInstance()
.sendUnreadItemsUpdateBroadcast();
}
}
adapter.close();
DBTasks.autodownloadUndownloadedItems(context);
@ -935,6 +911,26 @@ public class DBWriter {
});
}
/**
* Saves if a feed's last update failed
*
* @param lastUpdateFailed true if last update failed
*/
public static Future<?> setFeedLastUpdateFailed(final Context context,
final long feedId,
final boolean lastUpdateFailed) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed);
adapter.close();
}
});
}
/**
* format an url for querying the database
* (postfix a / and apply percent-encoding)

View File

@ -27,8 +27,11 @@ import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
;
// TODO Remove media column from feeditem table
/**
@ -151,6 +154,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_LAST_UPDATE_FAILED = "last_update_failed";
// Table names
public static final String TABLE_NAME_FEEDS = "Feeds";
@ -178,8 +182,8 @@ public class PodDBAdapter {
+ KEY_PASSWORD + " TEXT,"
+ KEY_IS_PAGED + " INTEGER DEFAULT 0,"
+ KEY_NEXT_PAGE_LINK + " TEXT,"
+ KEY_HIDE + " TEXT)";
+ KEY_HIDE + " TEXT,"
+ KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0)";
public static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@ -204,7 +208,8 @@ public class PodDBAdapter {
+ " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
+ KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+ KEY_FEEDITEM + " INTEGER,"
+ KEY_PLAYED_DURATION + " INTEGER)";
+ KEY_PLAYED_DURATION + " INTEGER,"
+ KEY_AUTO_DOWNLOAD + " INTEGER)";
public static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
@ -222,6 +227,28 @@ public class PodDBAdapter {
+ " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER,"
+ KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
// SQL Statements for creating indexes
public static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_FEED + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_FEED + ")";
public static final String CREATE_INDEX_FEEDITEMS_IMAGE = "CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_IMAGE + ")";
public static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " ("
+ KEY_FEEDITEM + ")";
public static final String CREATE_INDEX_FEEDMEDIA_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_FEED_MEDIA + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_FEED_MEDIA + " ("
+ KEY_FEEDITEM + ")";
public static final String CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_SIMPLECHAPTERS + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_SIMPLECHAPTERS + " ("
+ KEY_FEEDITEM + ")";
private SQLiteDatabase db;
private final Context context;
private PodDBHelper helper;
@ -250,7 +277,8 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_NEXT_PAGE_LINK,
TABLE_NAME_FEEDS + "." + KEY_USERNAME,
TABLE_NAME_FEEDS + "." + KEY_PASSWORD,
TABLE_NAME_FEEDS + "." + KEY_HIDE
TABLE_NAME_FEEDS + "." + KEY_HIDE,
TABLE_NAME_FEEDS + "." + KEY_LAST_UPDATE_FAILED,
};
// column indices for FEED_SEL_STD
@ -275,7 +303,6 @@ public class PodDBAdapter {
public static final int IDX_FEED_SEL_PREFERENCES_USERNAME = 18;
public static final int IDX_FEED_SEL_PREFERENCES_PASSWORD = 19;
/**
* Select all columns from the feeditems-table except description and
* content-encoded.
@ -407,7 +434,12 @@ public class PodDBAdapter {
values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong());
values.put(KEY_IS_PAGED, feed.isPaged());
values.put(KEY_NEXT_PAGE_LINK, feed.getNextPageLink());
values.put(KEY_HIDE, StringUtils.join(feed.getItemFilter(), ","));
if(feed.getItemFilter() != null && feed.getItemFilter().getValues().length > 0) {
values.put(KEY_HIDE, StringUtils.join(feed.getItemFilter().getValues(), ","));
} else {
values.put(KEY_HIDE, "");
}
values.put(KEY_LAST_UPDATE_FAILED, feed.hasLastUpdateFailed());
if (feed.getId() == 0) {
// Create new entry
Log.d(this.toString(), "Inserting new Feed into db");
@ -779,6 +811,13 @@ public class PodDBAdapter {
}
}
public void setFeedLastUpdateFailed(long feedId, boolean failed) {
final String sql = "UPDATE " + TABLE_NAME_FEEDS
+ " SET " + KEY_LAST_UPDATE_FAILED+ "=" + (failed ? "1" : "0")
+ " WHERE " + KEY_ID + "="+ feedId;
db.execSQL(sql);
}
/**
* Inserts or updates a download status.
*/
@ -1049,11 +1088,43 @@ public class PodDBAdapter {
return c;
}
public final Cursor getUnreadItemIdsCursor() {
Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID},
KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC");
return c;
public final Cursor getNewItemIdsCursor() {
final String query = "SELECT " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ " WHERE "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
return db.rawQuery(query, null);
}
/**
* Returns a cursor which contains all feed items that are considered new.
* The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection.
*/
public final Cursor getNewItemsCursor() {
final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ " WHERE "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL" // not in queue
+ " ORDER BY " + KEY_PUBDATE + " DESC";
Cursor c = db.rawQuery(query, null);
return c;
}
public final Cursor getRecentlyPublishedItemsCursor(int limit) {
@ -1149,7 +1220,7 @@ public class PodDBAdapter {
}
public final Cursor getFeedItemCursor(final String id) {
return getFeedItemCursor(new String[] { id });
return getFeedItemCursor(new String[]{id});
}
public final Cursor getFeedItemCursor(final String[] ids) {
@ -1199,9 +1270,20 @@ public class PodDBAdapter {
return result;
}
public final int getNumberOfUnreadItems() {
final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS +
" WHERE " + KEY_READ + " = 0";
public final int getNumberOfNewItems() {
final String query = "SELECT COUNT(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ")"
+" FROM " + TABLE_NAME_FEED_ITEMS
+ " LEFT JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ " LEFT JOIN " + TABLE_NAME_QUEUE + " ON "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ " WHERE "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
Cursor c = db.rawQuery(query, null);
int result = 0;
if (c.moveToFirst()) {
@ -1211,6 +1293,25 @@ public class PodDBAdapter {
return result;
}
public final LongIntMap getNumberOfUnreadFeedItems(long... feedIds) {
final String query = "SELECT " + KEY_FEED + ", COUNT(" + KEY_ID + ") AS count "
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ " WHERE " + KEY_FEED + " IN (" + StringUtils.join(feedIds, ',') + ") "
+ " AND " + KEY_READ + " = 0"
+ " GROUP BY " + KEY_FEED;
Cursor c = db.rawQuery(query, null);
LongIntMap result = new LongIntMap(c.getCount());
if (c.moveToFirst()) {
do {
long feedId = c.getLong(0);
int count = c.getInt(1);
result.put(feedId, count);
} while(c.moveToNext());
}
c.close();
return result;
}
public final int getNumberOfDownloadedEpisodes() {
final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA +
" WHERE " + KEY_DOWNLOADED + " > 0";
@ -1372,6 +1473,13 @@ public class PodDBAdapter {
db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
db.execSQL(CREATE_TABLE_QUEUE);
db.execSQL(CREATE_TABLE_SIMPLECHAPTERS);
db.execSQL(CREATE_INDEX_FEEDITEMS_FEED);
db.execSQL(CREATE_INDEX_FEEDITEMS_IMAGE);
db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM);
db.execSQL(CREATE_INDEX_QUEUE_FEEDITEM);
db.execSQL(CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
}
@Override

View File

@ -0,0 +1,240 @@
package de.danoeh.antennapod.core.util;
import java.util.Arrays;
/**
* Fast and memory efficient int list
*/
public final class IntList {
private int[] values;
protected int size;
/**
* Constructs an empty instance with a default initial capacity.
*/
public IntList() {
this(4);
}
/**
* Constructs an empty instance.
*
* @param initialCapacity {@code >= 0;} initial capacity of the list
*/
public IntList(int initialCapacity) {
if(initialCapacity < 0) {
throw new IllegalArgumentException("initial capacity must be 0 or higher");
}
values = new int[initialCapacity];
size = 0;
}
@Override
public int hashCode() {
int hashCode = 1;
for (int i = 0; i < size; i++) {
int value = values[i];
hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
}
return hashCode;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (! (other instanceof IntList)) {
return false;
}
IntList otherList = (IntList) other;
if (size != otherList.size) {
return false;
}
for (int i = 0; i < size; i++) {
if (values[i] != otherList.values[i]) {
return false;
}
}
return true;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer(size * 5 + 10);
sb.append("IntList{");
for (int i = 0; i < size; i++) {
if (i != 0) {
sb.append(", ");
}
sb.append(values[i]);
}
sb.append("}");
return sb.toString();
}
/**
* Gets the number of elements in this list.
*/
public int size() {
return size;
}
/**
* Gets the indicated value.
*
* @param n {@code >= 0, < size();} which element
* @return the indicated element's value
*/
public int get(int n) {
if (n >= size) {
throw new IndexOutOfBoundsException("n >= size()");
} else if(n < 0) {
throw new IndexOutOfBoundsException("n < 0");
}
return values[n];
}
/**
* Sets the value at the given index.
*
* @param index the index at which to put the specified object.
* @param value the object to add.
* @return the previous element at the index.
*/
public int set(int index, int value) {
if (index >= size) {
throw new IndexOutOfBoundsException("n >= size()");
} else if(index < 0) {
throw new IndexOutOfBoundsException("n < 0");
}
int result = values[index];
values[index] = value;
return result;
}
/**
* Adds an element to the end of the list. This will increase the
* list's capacity if necessary.
*
* @param value the value to add
*/
public void add(int value) {
growIfNeeded();
values[size++] = value;
}
/**
* Inserts element into specified index, moving elements at and above
* that index up one. May not be used to insert at an index beyond the
* current size (that is, insertion as a last element is legal but
* no further).
*
* @param n {@code >= 0, <=size();} index of where to insert
* @param value value to insert
*/
public void insert(int n, int value) {
if (n > size) {
throw new IndexOutOfBoundsException("n > size()");
} else if(n < 0) {
throw new IndexOutOfBoundsException("n < 0");
}
growIfNeeded();
System.arraycopy (values, n, values, n+1, size - n);
values[n] = value;
size++;
}
/**
* Removes value from this list.
*
* @param value value to remove
* return {@code true} if the value was removed, {@code false} otherwise
*/
public boolean remove(int value) {
for (int i = 0; i < size; i++) {
if (values[i] == value) {
size--;
System.arraycopy(values, i+1, values, i, size-i);
return true;
}
}
return false;
}
/**
* Removes an element at a given index, shifting elements at greater
* indicies down one.
*
* @param index index of element to remove
*/
public void removeIndex(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("n >= size()");
} else if(index < 0) {
throw new IndexOutOfBoundsException("n < 0");
}
size--;
System.arraycopy (values, index + 1, values, index, size - index);
}
/**
* Increases size of array if needed
*/
private void growIfNeeded() {
if (size == values.length) {
// Resize.
int[] newArray = new int[size * 3 / 2 + 10];
System.arraycopy(values, 0, newArray, 0, size);
values = newArray;
}
}
/**
* Returns the index of the given value, or -1 if the value does not
* appear in the list.
*
* @param value value to find
* @return index of value or -1
*/
public int indexOf(int value) {
for (int i = 0; i < size; i++) {
if (values[i] == value) {
return i;
}
}
return -1;
}
/**
* Removes all values from this list.
*/
public void clear() {
values = new int[4];
size = 0;
}
/**
* Returns true if the given value is contained in the list
*
* @param value value to look for
* @return {@code true} if this list contains {@code value}, {@code false} otherwise
*/
public boolean contains(int value) {
return indexOf(value) >= 0;
}
/**
* Returns an array with a copy of this list's values
*
* @return array with a copy of this list's values
*/
public int[] toArray() {
return Arrays.copyOf(values, size);
}
}

View File

@ -0,0 +1,252 @@
package de.danoeh.antennapod.core.util;
/**
* Fast and memory efficient long to long map
*/
public class LongIntMap {
private long[] keys;
private int[] values;
private int size;
/**
* Creates a new LongLongMap containing no mappings.
*/
public LongIntMap() {
this(10);
}
/**
* Creates a new SparseLongArray containing no mappings that will not
* require any additional memory allocation to store the specified
* number of mappings. If you supply an initial capacity of 0, the
* sparse array will be initialized with a light-weight representation
* not requiring any additional array allocations.
*/
public LongIntMap(int initialCapacity) {
if(initialCapacity < 0) {
throw new IllegalArgumentException("initial capacity must be 0 or higher");
}
keys = new long[initialCapacity];
values = new int[initialCapacity];
size = 0;
}
/**
* Increases size of array if needed
*/
private void growIfNeeded() {
if (size == keys.length) {
// Resize.
long[] newKeysArray = new long[size * 3 / 2 + 10];
int[] newValuesArray = new int[size * 3 / 2 + 10];
System.arraycopy(keys, 0, newKeysArray, 0, size);
System.arraycopy(values, 0, newValuesArray, 0, size);
keys = newKeysArray;
values = newValuesArray;
}
}
/**
* Gets the long mapped from the specified key, or <code>0</code>
* if no such mapping has been made.
*/
public int get(long key) {
return get(key, 0);
}
/**
* Gets the long mapped from the specified key, or the specified value
* if no such mapping has been made.
*/
public int get(long key, int valueIfKeyNotFound) {
int index = indexOfKey(key);
if(index >= 0) {
return values[index];
} else {
return valueIfKeyNotFound;
}
}
/**
* Removes the mapping from the specified key, if there was any.
*/
public boolean delete(long key) {
int index = indexOfKey(key);
if (index >= 0) {
removeAt(index);
return true;
} else {
return false;
}
}
/**
* Removes the mapping at the given index.
*/
public void removeAt(int index) {
System.arraycopy(keys, index + 1, keys, index, size - (index + 1));
System.arraycopy(values, index + 1, values, index, size - (index + 1));
size--;
}
/**
* Adds a mapping from the specified key to the specified value,
* replacing the previous mapping from the specified key if there
* was one.
*/
public void put(long key, int value) {
int index = indexOfKey(key);
if (index >= 0) {
values[index] = value;
} else {
growIfNeeded();
keys[size] = key;
values[size] = value;
size++;
}
}
/**
* Returns the number of key-value mappings that this SparseIntArray
* currently stores.
*/
public int size() {
return size;
}
/**
* Given an index in the range <code>0...size()-1</code>, returns
* the key from the <code>index</code>th key-value mapping that this
* SparseLongArray stores.
*
* <p>The keys corresponding to indices in ascending order are guaranteed to
* be in ascending order, e.g., <code>keyAt(0)</code> will return the
* smallest key and <code>keyAt(size()-1)</code> will return the largest
* key.</p>
*/
public long keyAt(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("n >= size()");
} else if(index < 0) {
throw new IndexOutOfBoundsException("n < 0");
}
return keys[index];
}
/**
* Given an index in the range <code>0...size()-1</code>, returns
* the value from the <code>index</code>th key-value mapping that this
* SparseLongArray stores.
*
* <p>The values corresponding to indices in ascending order are guaranteed
* to be associated with keys in ascending order, e.g.,
* <code>valueAt(0)</code> will return the value associated with the
* smallest key and <code>valueAt(size()-1)</code> will return the value
* associated with the largest key.</p>
*/
public int valueAt(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("n >= size()");
} else if(index < 0) {
throw new IndexOutOfBoundsException("n < 0");
}
return values[index];
}
/**
* Returns the index for which {@link #keyAt} would return the
* specified key, or a negative number if the specified
* key is not mapped.
*/
public int indexOfKey(long key) {
for(int i=0; i < size; i++) {
if(keys[i] == key) {
return i;
}
}
return -1;
}
/**
* Returns an index for which {@link #valueAt} would return the
* specified key, or a negative number if no keys map to the
* specified value.
* Beware that this is a linear search, unlike lookups by key,
* and that multiple keys can map to the same value and this will
* find only one of them.
*/
public int indexOfValue(long value) {
for (int i = 0; i < size; i++) {
if (values[i] == value) {
return i;
}
}
return -1;
}
/**
* Removes all key-value mappings from this SparseIntArray.
*/
public void clear() {
keys = new long[10];
values = new int[10];
size = 0;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (! (other instanceof LongIntMap)) {
return false;
}
LongIntMap otherMap = (LongIntMap) other;
if (size != otherMap.size) {
return false;
}
for (int i = 0; i < size; i++) {
if (keys[i] != otherMap.keys[i] ||
values[i] != otherMap.values[i]) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int hashCode = 1;
for (int i = 0; i < size; i++) {
long value = values[i];
hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
}
return hashCode;
}
@Override
public String toString() {
if (size() <= 0) {
return "LongLongMap{}";
}
StringBuilder buffer = new StringBuilder(size * 28);
buffer.append("LongLongMap{");
for (int i=0; i < size; i++) {
if (i > 0) {
buffer.append(", ");
}
long key = keyAt(i);
buffer.append(key);
buffer.append('=');
long value = valueAt(i);
buffer.append(value);
}
buffer.append('}');
return buffer.toString();
}
}

View File

@ -32,7 +32,6 @@ public final class LongList {
@Override
public int hashCode() {
Arrays.hashCode(values);
int hashCode = 1;
for (int i = 0; i < size; i++) {
long value = values[i];

View File

@ -80,10 +80,10 @@
<string name="browse_gpoddernet_label">Browse gpodder.net</string>
<!-- Actions on feeds -->
<string name="mark_all_read_label">Mark all as read</string>
<string name="mark_all_read_msg">Marked all episodes as read</string>
<string name="mark_all_read_confirmation_msg">Please confirm that you want to mark all episodes as being read.</string>
<string name="mark_all_read_feed_confirmation_msg">Please confirm that you want to mark all episodes in this feed as being read.</string>
<string name="mark_all_read_label">Mark all as played</string>
<string name="mark_all_read_msg">Marked all episodes as played</string>
<string name="mark_all_read_confirmation_msg">Please confirm that you want to mark all episodes as being played.</string>
<string name="mark_all_read_feed_confirmation_msg">Please confirm that you want to mark all episodes in this feed as being played.</string>
<string name="show_info_label">Show information</string>
<string name="remove_feed_label">Remove podcast</string>
<string name="share_link_label">Share website link</string>
@ -100,6 +100,7 @@
<string name="hide_downloaded_episodes_label">Downloaded</string>
<string name="hide_not_downloaded_episodes_label">Not downloaded</string>
<string name="filtered_label">Filtered</string>
<string name="refresh_failed_msg">{fa-exclamation-circle} Last refresh failed</string>
<!-- actions on feeditems -->
<string name="download_label">Download</string>
@ -120,6 +121,9 @@
<string name="enqueue_all_new">Enqueue all</string>
<string name="download_all">Download all</string>
<string name="skip_episode_label">Skip episode</string>
<string name="activate_auto_download">Activate auto download</string>
<string name="deactivate_auto_download">Deactivate auto download</string>
<string name="reset_position">Reset playback position</string>
<!-- Download messages and labels -->
<string name="download_successful">successful</string>

Binary file not shown.

View File

@ -1,6 +1,6 @@
#Fri Nov 28 16:00:13 CET 2014
#Tue May 19 11:59:21 CEST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip