Merge pull request #808 from mfietz/feature/nav-indicators+new-redone
Nav indicators, context menus and new "new"
This commit is contained in:
commit
ba036e1499
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
|
@ -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>
|
|
@ -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>
|
|
@ -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'
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 {
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
|
|
|
@ -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.
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue