Merged queue and unread list

This commit is contained in:
daniel oeh 2013-01-27 00:53:06 +01:00
parent 96aa72c0ab
commit be9018e843
13 changed files with 453 additions and 134 deletions

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ExpandableListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ExpandableListView>
</LinearLayout>

View File

@ -4,26 +4,16 @@
android:layout_height="match_parent"
android:background="?attr/borderless_button" >
<ImageView
android:id="@+id/imgvHeaderArrow"
android:layout_width="@dimen/thumbnail_length"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:padding="12dp"
android:scaleType="fitEnd"
android:src="?attr/navigation_expand" />
<TextView
android:id="@+id/txtvHeaderTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginBottom="24dp"
android:layout_marginLeft="8dp"
android:layout_marginLeft="@dimen/thumbnail_length"
android:layout_marginRight="16dp"
android:layout_marginTop="24dp"
android:layout_toRightOf="@id/imgvHeaderArrow"
android:paddingLeft="8dp"
android:textColor="@color/bright_blue"
android:textSize="@dimen/text_size_large"
android:textStyle="bold" />

View File

@ -4,6 +4,8 @@
<!-- Activitiy titles -->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Feeds</string>
<string name="podcasts_label">Podcasts</string>
<string name="episodes_label">Episodes</string>
<string name="new_label">New</string>
<string name="settings_label">Settings</string>
<string name="add_new_feed_label">Add a new Feed</string>

View File

@ -23,6 +23,7 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.QueueFragment;
@ -71,8 +72,8 @@ public class MainActivity extends SherlockFragmentActivity {
&& getIntent().getAction().equals(Intent.ACTION_MAIN)) {
appLaunched = true;
if (manager.getUnreadItems().size() > 0) {
viewpager.setCurrentItem(MainPagerAdapter.POS_NEW_ITEMS);
viewpager.setCurrentItem(MainPagerAdapter.POS_EPISODES);
}
}
}
@ -165,11 +166,10 @@ public class MainActivity extends SherlockFragmentActivity {
}
public static class MainPagerAdapter extends FragmentStatePagerAdapter {
private static final int NUM_ITEMS = 3;
private static final int NUM_ITEMS = 2;
public static final int POS_FEEDLIST = 0;
public static final int POS_NEW_ITEMS = 1;
public static final int POS_QUEUE = 2;
public static final int POS_EPISODES = 1;
private Context context;
@ -183,10 +183,9 @@ public class MainActivity extends SherlockFragmentActivity {
switch (position) {
case POS_FEEDLIST:
return new FeedlistFragment();
case POS_NEW_ITEMS:
return new UnreadItemlistFragment();
case POS_QUEUE:
return new QueueFragment();
case POS_EPISODES:
return new EpisodesFragment();
default:
return null;
}
@ -201,11 +200,9 @@ public class MainActivity extends SherlockFragmentActivity {
public CharSequence getPageTitle(int position) {
switch (position) {
case POS_FEEDLIST:
return context.getString(R.string.feeds_label);
case POS_NEW_ITEMS:
return context.getString(R.string.new_label);
case POS_QUEUE:
return context.getString(R.string.queue_label);
return context.getString(R.string.podcasts_label);
case POS_EPISODES:
return context.getString(R.string.episodes_label);
default:
return null;
}

View File

@ -8,22 +8,28 @@ import de.danoeh.antennapod.util.EpisodeFilter;
import android.content.Context;
import android.widget.ArrayAdapter;
public abstract class AbstractFeedItemlistAdapter extends ArrayAdapter<FeedItem> {
public abstract class AbstractFeedItemlistAdapter extends
ArrayAdapter<FeedItem> {
private List<FeedItem> objects;
private boolean isExpanded = true;
public AbstractFeedItemlistAdapter(Context context, int textViewResourceId,
List<FeedItem> objects) {
super(context, textViewResourceId, objects);
this.objects = objects;
}
@Override
public int getCount() {
if (PodcastApp.getInstance().displayOnlyEpisodes()) {
return EpisodeFilter.countItemsWithEpisodes(objects);
if (isExpanded) {
if (PodcastApp.getInstance().displayOnlyEpisodes()) {
return EpisodeFilter.countItemsWithEpisodes(objects);
} else {
return super.getCount();
}
} else {
return super.getCount();
return 0;
}
}
@ -35,4 +41,9 @@ public abstract class AbstractFeedItemlistAdapter extends ArrayAdapter<FeedItem>
return super.getItem(position);
}
}
public void toggleExpandedState() {
isExpanded = !isExpanded;
notifyDataSetChanged();
}
}

View File

@ -1,6 +1,8 @@
package de.danoeh.antennapod.adapter;
import de.danoeh.antennapod.feed.FeedItem;
public interface ActionButtonCallback {
/** Is called when the action button of a list item has been pressed. */
abstract void onActionButtonPressed(int position);
abstract void onActionButtonPressed(FeedItem item);
}

View File

@ -0,0 +1,202 @@
package de.danoeh.antennapod.adapter;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FeedImageLoader;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.util.EpisodeFilter;
/**
* Displays unread items and items in the queue in one combined list. The
* structure of this list is: [header] [queueItems] [header] [unreadItems].
*/
public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
private static final String TAG = "ExternalEpisodesListAdapter";
public static final int GROUP_POS_QUEUE = 0;
public static final int GROUP_POS_UNREAD = 1;
private Context context;
private List<FeedItem> unreadItems;
private List<FeedItem> queueItems;
ActionButtonCallback callback;
public ExternalEpisodesListAdapter(Context context,
List<FeedItem> unreadItems, List<FeedItem> queueItems,
ActionButtonCallback callback) {
super();
this.context = context;
this.unreadItems = unreadItems;
this.queueItems = queueItems;
this.callback = callback;
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
@Override
public FeedItem getChild(int groupPosition, int childPosition) {
final boolean displayOnlyEpisodes = PodcastApp.getInstance()
.displayOnlyEpisodes();
if (groupPosition == GROUP_POS_QUEUE) {
if (displayOnlyEpisodes) {
return EpisodeFilter.accessEpisodeByIndex(queueItems,
childPosition);
} else {
return queueItems.get(childPosition);
}
} else if (groupPosition == GROUP_POS_UNREAD) {
if (displayOnlyEpisodes) {
return EpisodeFilter.accessEpisodeByIndex(unreadItems,
childPosition);
} else {
return unreadItems.get(childPosition);
}
}
return null;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
Holder holder;
final FeedItem item = getChild(groupPosition, childPosition);
if (convertView == null) {
holder = new Holder();
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.external_itemlist_item,
null);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.feedImage = (ImageView) convertView
.findViewById(R.id.imgvFeedimage);
holder.butAction = (ImageButton) convertView
.findViewById(R.id.butAction);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
holder.title.setText(item.getTitle());
holder.feedImage.setTag(item.getFeed().getImage());
FeedImageLoader.getInstance().loadThumbnailBitmap(
item.getFeed().getImage(),
holder.feedImage,
(int) convertView.getResources().getDimension(
R.dimen.thumbnail_length));
holder.butAction.setFocusable(false);
holder.butAction.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
callback.onActionButtonPressed(item);
}
});
return convertView;
}
static class Holder {
TextView title;
ImageView feedImage;
ImageButton butAction;
}
@Override
public int getChildrenCount(int groupPosition) {
final boolean displayOnlyEpisodes = PodcastApp.getInstance()
.displayOnlyEpisodes();
if (groupPosition == GROUP_POS_QUEUE) {
if (displayOnlyEpisodes) {
return EpisodeFilter.countItemsWithEpisodes(queueItems);
} else {
return queueItems.size();
}
} else if (groupPosition == GROUP_POS_UNREAD) {
if (displayOnlyEpisodes) {
return EpisodeFilter.countItemsWithEpisodes(unreadItems);
} else {
return unreadItems.size();
}
}
return 0;
}
@Override
public int getGroupCount() {
return 2;
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.feeditemlist_header, null);
TextView headerTitle = (TextView) convertView
.findViewById(R.id.txtvHeaderTitle);
String headerString = null;
if (groupPosition == 0) {
headerString = context.getString(R.string.queue_label);
if (!queueItems.isEmpty()) {
headerString += " (" + queueItems.size() + ")";
}
} else {
headerString = context.getString(R.string.new_label);
if (!unreadItems.isEmpty()) {
headerString += " (" + unreadItems.size() + ")";
}
}
headerTitle.setText(headerString);
return convertView;
}
@Override
public boolean isEmpty() {
return unreadItems.isEmpty() && queueItems.isEmpty();
}
@Override
public Object getGroup(int groupPosition) {
return null;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}

View File

@ -62,7 +62,7 @@ public class ExternalFeedItemlistAdapter extends AbstractFeedItemlistAdapter {
@Override
public void onClick(View v) {
callback.onActionButtonPressed(position);
//callback.onActionButtonPressed(position);
}
});

View File

@ -50,7 +50,7 @@ public class FeedItemlistAdapter extends AbstractFeedItemlistAdapter {
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
Holder holder;
FeedItem item = getItem(position);
final FeedItem item = getItem(position);
if (convertView == null) {
holder = new Holder();
@ -185,7 +185,7 @@ public class FeedItemlistAdapter extends AbstractFeedItemlistAdapter {
@Override
public void onClick(View v) {
callback.onActionButtonPressed(position);
callback.onActionButtonPressed(item);
}
});

View File

@ -1,55 +0,0 @@
package de.danoeh.antennapod.adapter;
import de.danoeh.antennapod.R;
import android.content.res.TypedArray;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
/**
* OnClickListener for the itemlist headers of feeditem lists. This class takes
* care of changing the appearance of the arrow on the left side of the header
* view. An instance of this class should be set as the OnClickListener of the
* header view.
*/
public class OnItemlistHeaderClicked implements OnClickListener {
private ImageView arrow;
private View header;
private boolean isExpanded;
/**
* Constructor
*
* @param header
* Reference to the header View of the itemlist.
* @param isExpanded
* true if the itemlist is currently expanded.
* */
public OnItemlistHeaderClicked(View header, boolean isExpanded) {
if (header == null)
throw new IllegalArgumentException("Header view must not be null");
this.header = header;
arrow = (ImageView) header.findViewById(R.id.imgvHeaderArrow);
this.isExpanded = isExpanded;
refreshArrowState();
}
private void refreshArrowState() {
TypedArray typeDrawables = header.getContext().obtainStyledAttributes(
new int[] { R.attr.navigation_collapse,
R.attr.navigation_expand });
if (isExpanded) {
arrow.setImageDrawable(typeDrawables.getDrawable(0));
} else {
arrow.setImageDrawable(typeDrawables.getDrawable(1));
}
}
@Override
public void onClick(View v) {
isExpanded = !isExpanded;
refreshArrowState();
}
}

View File

@ -0,0 +1,171 @@
package de.danoeh.antennapod.fragment;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import com.actionbarsherlock.app.SherlockFragment;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.ItemviewActivity;
import de.danoeh.antennapod.adapter.ActionButtonCallback;
import de.danoeh.antennapod.adapter.ExternalEpisodesListAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
public class EpisodesFragment extends SherlockFragment {
private static final String TAG = "EpisodesFragment";
private ExpandableListView listView;
private ExternalEpisodesListAdapter adapter;
protected FeedItem selectedItem = null;
protected boolean contextMenuClosed = true;
@Override
public void onPause() {
super.onPause();
try {
getActivity().unregisterReceiver(contentUpdate);
} catch (IllegalArgumentException e) {
}
}
@Override
public void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(DownloadRequester.ACTION_DOWNLOAD_QUEUED);
filter.addAction(DownloadService.ACTION_DOWNLOAD_HANDLED);
filter.addAction(FeedManager.ACTION_QUEUE_UPDATE);
filter.addAction(FeedManager.ACTION_UNREAD_ITEMS_UPDATE);
getActivity().registerReceiver(contentUpdate, filter);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.episodes_fragment, null);
listView = (ExpandableListView) v.findViewById(android.R.id.list);
return v;
}
protected ActionButtonCallback adapterCallback = new ActionButtonCallback() {
@Override
public void onActionButtonPressed(FeedItem item) {
selectedItem = item;
contextMenuClosed = true;
listView.showContextMenu();
}
};
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
FeedManager manager = FeedManager.getInstance();
adapter = new ExternalEpisodesListAdapter(getActivity(),
manager.getUnreadItems(), manager.getQueue(), adapterCallback);
listView.setAdapter(adapter);
listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_QUEUE);
listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_UNREAD);
listView.setOnChildClickListener(new OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
FeedItem selection = adapter.getChild(groupPosition,
childPosition);
if (selection != null) {
Intent showItem = new Intent(getActivity(),
ItemviewActivity.class);
showItem.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED,
selection.getFeed().getId());
showItem.putExtra(ItemlistFragment.EXTRA_SELECTED_FEEDITEM,
selection.getId());
startActivity(showItem);
return true;
}
return true;
}
});
registerForContextMenu(listView);
}
private BroadcastReceiver contentUpdate = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received contentUpdate Intent.");
adapter.notifyDataSetChanged();
}
};
@Override
public void onCreateContextMenu(final ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (!contextMenuClosed) { // true if context menu was cancelled before
selectedItem = null;
}
contextMenuClosed = false;
listView.setOnItemLongClickListener(null);
if (selectedItem != null) {
new MenuInflater(getActivity()).inflate(R.menu.feeditem, menu);
menu.setHeaderTitle(selectedItem.getTitle());
FeedItemMenuHandler.onPrepareMenu(
new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
menu.findItem(id).setVisible(visible);
}
}, selectedItem, false);
}
}
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
boolean handled = false;
if (selectedItem != null) {
try {
handled = FeedItemMenuHandler.onMenuItemClicked(
getSherlockActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(
getActivity(), e.getMessage());
}
if (handled) {
adapter.notifyDataSetChanged();
}
}
selectedItem = null;
contextMenuClosed = true;
return handled;
}
}

View File

@ -59,8 +59,7 @@ public class ItemlistFragment extends SherlockListFragment {
*/
protected Feed feed;
protected static final int NO_SELECTION = -1;
protected int selectedPosition = NO_SELECTION;
protected FeedItem selectedItem = null;
protected boolean contextMenuClosed = true;
/** Argument for FeeditemlistAdapter */
@ -109,7 +108,7 @@ public class ItemlistFragment extends SherlockListFragment {
items = feed.getItems();
}
}
protected AbstractFeedItemlistAdapter createListAdapter() {
return new FeedItemlistAdapter(getActivity(), 0, items,
adapterCallback, showFeedtitle);
@ -194,10 +193,8 @@ public class ItemlistFragment extends SherlockListFragment {
protected ActionButtonCallback adapterCallback = new ActionButtonCallback() {
@Override
public void onActionButtonPressed(int position) {
if (AppConfig.DEBUG)
Log.d(TAG, "adapterCallback; position = " + position);
selectedPosition = position;
public void onActionButtonPressed(FeedItem item) {
selectedItem = item;
contextMenuClosed = true;
getListView().showContextMenu();
}
@ -218,26 +215,24 @@ public class ItemlistFragment extends SherlockListFragment {
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (!contextMenuClosed) { // true if context menu was cancelled before
selectedPosition = NO_SELECTION;
selectedItem = null;
}
contextMenuClosed = false;
getListView().setOnItemLongClickListener(null);
if (selectedPosition != NO_SELECTION) {
if (selectedItem != null) {
new MenuInflater(ItemlistFragment.this.getActivity()).inflate(
R.menu.feeditem, menu);
FeedItem selection = fila.getItem(selectedPosition);
if (selection != null) {
menu.setHeaderTitle(selection.getTitle());
FeedItemMenuHandler.onPrepareMenu(
new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id,
boolean visible) {
menu.findItem(id).setVisible(visible);
}
}, selection, false);
}
menu.setHeaderTitle(selectedItem.getTitle());
FeedItemMenuHandler.onPrepareMenu(
new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
menu.findItem(id).setVisible(visible);
}
}, selectedItem, false);
}
}
@ -245,25 +240,22 @@ public class ItemlistFragment extends SherlockListFragment {
public boolean onContextItemSelected(android.view.MenuItem item) {
boolean handled = false;
if (selectedPosition != NO_SELECTION) {
FeedItem selectedItem = fila.getItem(selectedPosition);
if (selectedItem != null) {
if (selectedItem != null) {
try {
handled = FeedItemMenuHandler.onMenuItemClicked(
getSherlockActivity(), item.getItemId(),
selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(
getActivity(), e.getMessage());
}
if (handled) {
fila.notifyDataSetChanged();
}
try {
handled = FeedItemMenuHandler.onMenuItemClicked(
getSherlockActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(
getActivity(), e.getMessage());
}
if (handled) {
fila.notifyDataSetChanged();
}
}
selectedPosition = NO_SELECTION;
selectedItem = null;
contextMenuClosed = true;
return handled;
}

View File

@ -69,13 +69,8 @@ public class UnreadItemlistFragment extends ItemlistFragment {
TextView headerTitle = (TextView) headerView
.findViewById(R.id.txtvHeaderTitle);
headerTitle.setText(R.string.new_label);
headerView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
}
});
getListView().addHeaderView(headerView);
super.onViewCreated(view, savedInstanceState);
}