diff --git a/app/build.gradle b/app/build.gradle index 37a149b36..9f8f204aa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,6 +14,7 @@ dependencies { compile "com.android.support:gridlayout-v7:$supportVersion" compile "com.android.support:cardview-v7:$supportVersion" compile "com.android.support:design:$supportVersion" + compile "com.android.support.recyclerview-v7:$supportVersion" compile "org.apache.commons:commons-lang3:$commonslangVersion" compile("org.shredzone.flattr4j:flattr4j-core:$flattr4jVersion") { exclude group: "org.json", module: "json" diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index e92599561..4899a5545 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -153,7 +153,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity navList.setAdapter(navAdapter); navList.setOnItemClickListener(navListClickListener); navList.setOnItemLongClickListener(newListLongClickListener); - registerForContextMenu(navList); + //registerForContextMenu(navList); navAdapter.registerDataSetObserver(new DataSetObserver() { @Override @@ -521,7 +521,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity return super.onOptionsItemSelected(item); } } - +/* @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); @@ -542,7 +542,8 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity // we may need to reference this elsewhere... lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; } - + */ +/* @Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); @@ -593,7 +594,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity return super.onContextItemSelected(item); } } - +*/ private DBReader.NavDrawerData navDrawerData; private AsyncTask loadTask; private int selectedNavListIndex = 0; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java new file mode 100644 index 000000000..36db8978f --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java @@ -0,0 +1,270 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.v7.widget.RecyclerView; +import android.text.format.DateUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; +import com.joanzapata.iconify.Iconify; + +import java.lang.ref.WeakReference; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.NetworkUtils; +import de.danoeh.antennapod.fragment.ItemFragment; + +/** + * List adapter for the list of new episodes + */ +public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter { + + private static final String TAG = AllEpisodesRecycleAdapter.class.getSimpleName(); + + private final Context context; + private final ItemAccess itemAccess; + private final ActionButtonCallback actionButtonCallback; + private final ActionButtonUtils actionButtonUtils; + private final boolean showOnlyNewEpisodes; + private final MainActivity mainActivity; + + public AllEpisodesRecycleAdapter(Context context, + MainActivity mainActivity, + ItemAccess itemAccess, + ActionButtonCallback actionButtonCallback, + boolean showOnlyNewEpisodes) { + super(); + this.mainActivity = mainActivity; + this.context = context; + this.itemAccess = itemAccess; + this.actionButtonUtils = new ActionButtonUtils(context); + this.actionButtonCallback = actionButtonCallback; + this.showOnlyNewEpisodes = showOnlyNewEpisodes; + } + + @Override + public Holder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.new_episodes_listitem, parent, false); + Holder holder = new Holder(view); + holder.placeholder = (TextView) view.findViewById(R.id.txtvPlaceholder); + holder.title = (TextView) view.findViewById(R.id.txtvTitle); + holder.pubDate = (TextView) view + .findViewById(R.id.txtvPublished); + holder.statusUnread = view.findViewById(R.id.statusUnread); + holder.butSecondary = (ImageButton) view + .findViewById(R.id.butSecondaryAction); + holder.queueStatus = (ImageView) view + .findViewById(R.id.imgvInPlaylist); + holder.progress = (ProgressBar) view + .findViewById(R.id.pbar_progress); + holder.cover = (ImageView) view.findViewById(R.id.imgvCover); + holder.txtvDuration = (TextView) view.findViewById(R.id.txtvDuration); + holder.itemId = 0; + holder.mainActivity = mainActivity; + + return holder; + } + + @Override + public void onBindViewHolder(final Holder holder, int position) { + final FeedItem item = itemAccess.getItem(position); + if (item == null) return; + holder.itemId = item.getId(); + holder.placeholder.setVisibility(View.VISIBLE); + holder.placeholder.setText(item.getFeed().getTitle()); + holder.title.setText(item.getTitle()); + holder.pubDate.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL)); + if (showOnlyNewEpisodes || false == item.isNew()) { + holder.statusUnread.setVisibility(View.INVISIBLE); + } else { + holder.statusUnread.setVisibility(View.VISIBLE); + } + + FeedMedia media = item.getMedia(); + if (media != null) { + final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media); + + if (media.getDuration() > 0) { + holder.txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); + } else if (media.getSize() > 0) { + holder.txtvDuration.setText(Converter.byteToString(media.getSize())); + } else if(false == media.checkedOnSizeButUnknown()) { + holder.txtvDuration.setText("{fa-spinner}"); + Iconify.addIcons(holder.txtvDuration); + NetworkUtils.getFeedMediaSizeObservable(media) + .subscribe( + size -> { + if (size > 0) { + holder.txtvDuration.setText(Converter.byteToString(size)); + } else { + holder.txtvDuration.setText(""); + } + }, error -> { + holder.txtvDuration.setText(""); + Log.e(TAG, Log.getStackTraceString(error)); + }); + } else { + holder.txtvDuration.setText(""); + } + + FeedItem.State state = item.getState(); + if (isDownloadingMedia) { + holder.progress.setVisibility(View.VISIBLE); + // item is being downloaded + holder.progress.setProgress(itemAccess.getItemDownloadProgressPercent(item)); + } else if (state == FeedItem.State.PLAYING + || state == FeedItem.State.IN_PROGRESS) { + if (media.getDuration() > 0) { + int progress = (int) (100.0 * media.getPosition() / media.getDuration()); + holder.progress.setProgress(progress); + holder.progress.setVisibility(View.VISIBLE); + } + } else { + holder.progress.setVisibility(View.GONE); + } + + } else { + holder.progress.setVisibility(View.GONE); + holder.txtvDuration.setVisibility(View.GONE); + } + + if (itemAccess.isInQueue(item)) { + holder.queueStatus.setVisibility(View.VISIBLE); + } else { + holder.queueStatus.setVisibility(View.INVISIBLE); + } + + actionButtonUtils.configureActionButton(holder.butSecondary, item); + holder.butSecondary.setFocusable(false); + holder.butSecondary.setTag(item); + holder.butSecondary.setOnClickListener(secondaryActionListener); + + Glide.with(context) + .load(item.getImageUri()) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(item.getFeed().getImageUri(), holder.placeholder, holder.cover)); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getItemCount() { + return itemAccess.getCount(); + } + + public FeedItem getItem(int position) { + return itemAccess.getItem(position); + } + + private class CoverTarget extends GlideDrawableImageViewTarget { + + private final WeakReference fallback; + private final WeakReference placeholder; + private final WeakReference cover; + + public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover) { + super(imgvCover); + fallback = new WeakReference<>(fallbackUri); + placeholder = new WeakReference<>(txtvPlaceholder); + cover = new WeakReference<>(imgvCover); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + Uri fallbackUri = fallback.get(); + TextView txtvPlaceholder = placeholder.get(); + ImageView imgvCover = cover.get(); + if(fallbackUri != null && txtvPlaceholder != null && imgvCover != null) { + Glide.with(context) + .load(fallbackUri) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(null, txtvPlaceholder, imgvCover)); + } + } + + @Override + public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) { + super.onResourceReady(drawable, anim); + TextView txtvPlaceholder = placeholder.get(); + if(txtvPlaceholder != null) { + txtvPlaceholder.setVisibility(View.INVISIBLE); + } + } + } + + private View.OnClickListener secondaryActionListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + FeedItem item = (FeedItem) v.getTag(); + actionButtonCallback.onActionButtonPressed(item); + } + }; + + + public static class Holder extends RecyclerView.ViewHolder + implements View.OnClickListener { + TextView placeholder; + TextView title; + TextView pubDate; + View statusUnread; + ImageView queueStatus; + ImageView cover; + ProgressBar progress; + TextView txtvDuration; + ImageButton butSecondary; + long itemId; + MainActivity mainActivity; + + public Holder(View itemView) { + super(itemView); + itemView.setOnClickListener(this); + itemView.setOnCreateContextMenuListener(mainActivity); + } + + @Override + public void onClick(View v) { + if (mainActivity != null) { + mainActivity.loadChildFragment(ItemFragment.newInstance(itemId)); + } + } + } + + public interface ItemAccess { + + int getCount(); + + FeedItem getItem(int position); + + int getItemDownloadProgressPercent(FeedItem item); + + boolean isInQueue(FeedItem item); + + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java index 31b24773f..434740af8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -9,7 +9,10 @@ import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.util.Pair; import android.support.v4.view.MenuItemCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; +import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -23,14 +26,13 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import com.mobeta.android.dslv.DragSortListView; - import java.util.List; import java.util.concurrent.atomic.AtomicReference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.AllEpisodesListAdapter; +import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; import de.danoeh.antennapod.core.asynctask.DownloadObserver; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; @@ -72,8 +74,8 @@ public class AllEpisodesFragment extends Fragment { private static final String PREF_KEY_LIST_SELECTION = "list_selection"; private String prefName; - protected DragSortListView listView; - private AllEpisodesListAdapter listAdapter; + protected RecyclerView listView; + private AllEpisodesRecycleAdapter listAdapter; private TextView txtvEmpty; private ProgressBar progLoading; private ContextMenu contextMenu; @@ -94,6 +96,7 @@ public class AllEpisodesFragment extends Fragment { private boolean isUpdatingFeeds; protected Subscription subscription; + private RecyclerView.LayoutManager layoutManager; public AllEpisodesFragment() { // by default we show all the episodes @@ -167,8 +170,7 @@ public class AllEpisodesFragment extends Fragment { SharedPreferences.Editor editor = prefs.edit(); View v = listView.getChildAt(0); int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop()); - editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition()); - editor.putInt(PREF_KEY_LIST_TOP, top); + editor.putInt(PREF_KEY_LIST_SELECTION, listView.getVerticalScrollbarPosition()); editor.commit(); } @@ -177,11 +179,10 @@ public class AllEpisodesFragment extends Fragment { int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0); int top = prefs.getInt(PREF_KEY_LIST_TOP, 0); if (listSelection > 0 || top > 0) { - listView.setSelectionFromTop(listSelection, top); + listView.scrollToPosition(listSelection); // restore once, then forget SharedPreferences.Editor editor = prefs.edit(); editor.putInt(PREF_KEY_LIST_SELECTION, 0); - editor.putInt(PREF_KEY_LIST_TOP, 0); editor.commit(); } } @@ -289,21 +290,13 @@ public class AllEpisodesFragment extends Fragment { View root = inflater.inflate(fragmentResource, container, false); - listView = (DragSortListView) root.findViewById(android.R.id.list); + listView = (RecyclerView) root.findViewById(android.R.id.list); + layoutManager = new LinearLayoutManager(getActivity()); + listView.setLayoutManager(layoutManager); + txtvEmpty = (TextView) root.findViewById(android.R.id.empty); progLoading = (ProgressBar) root.findViewById(R.id.progLoading); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - FeedItem item = (FeedItem) listAdapter.getItem(position - listView.getHeaderViewsCount()); - if (item != null) { - ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId())); - } - - } - }); - registerForContextMenu(listView); if (!itemsLoaded) { @@ -336,6 +329,7 @@ public class AllEpisodesFragment extends Fragment { @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); + if (menuInfo == null) { return; } AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; FeedItem item = itemAccess.getItem(adapterInfo.position); MenuInflater inflater = getActivity().getMenuInflater(); @@ -357,7 +351,7 @@ public class AllEpisodesFragment extends Fragment { return false; } AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); - if (menuInfo == null) { + if (menuInfo == null) { menuInfo = lastMenuInfo; } if (menuInfo == null) { @@ -390,10 +384,9 @@ public class AllEpisodesFragment extends Fragment { private void onFragmentLoaded() { if (listAdapter == null) { - listAdapter = new AllEpisodesListAdapter(activity.get(), itemAccess, + listAdapter = new AllEpisodesRecycleAdapter(activity.get(), activity.get(), itemAccess, new DefaultActionButtonCallback(activity.get()), showOnlyNewEpisodes); listView.setAdapter(listAdapter); - listView.setEmptyView(txtvEmpty); downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback); downloadObserver.onResume(); } @@ -413,7 +406,7 @@ public class AllEpisodesFragment extends Fragment { } }; - private AllEpisodesListAdapter.ItemAccess itemAccess = new AllEpisodesListAdapter.ItemAccess() { + protected AllEpisodesRecycleAdapter.ItemAccess itemAccess = new AllEpisodesRecycleAdapter.ItemAccess() { @Override public int getCount() { @@ -467,7 +460,6 @@ public class AllEpisodesFragment extends Fragment { }; private void updateShowOnlyEpisodesListViewState() { - listView.setEmptyView(txtvEmpty); } protected void loadItems() { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java index 95f7cfcc1..27fbbacf8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java @@ -66,6 +66,7 @@ public class FavoriteEpisodesFragment extends AllEpisodesFragment { View root = super.onCreateViewHelper(inflater, container, savedInstanceState, R.layout.episodes_fragment_with_undo); + /** TODO! listView.setRemoveListener(which -> { Log.d(TAG, "remove(" + which + ")"); if (subscription != null) { @@ -80,6 +81,7 @@ public class FavoriteEpisodesFragment extends AllEpisodesFragment { which) ); }); + */ undoBarController = new UndoBarController(root.findViewById(R.id.undobar), new UndoBarController.UndoListener() { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java index 60d0161b2..5332f2025 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -69,6 +69,7 @@ public class NewEpisodesFragment extends AllEpisodesFragment { View root = super.onCreateViewHelper(inflater, container, savedInstanceState, R.layout.episodes_fragment_with_undo); + /** TODO! listView.setRemoveListener(new DragSortListView.RemoveListener() { @Override public void remove(int which) { @@ -86,6 +87,7 @@ public class NewEpisodesFragment extends AllEpisodesFragment { ); } }); + */ undoBarController = new UndoBarController(root.findViewById(R.id.undobar), new UndoBarController.UndoListener() { diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java index b24779934..3fa1048c0 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -73,10 +73,10 @@ public class FeedItemMenuHandler { } boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE); - if(queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId()) { + if(queueAccess == null || 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()) { + if(queueAccess == null || queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == selectedItem.getId()) { mi.setItemVisibility(R.id.move_to_bottom_item, false); } if (!isInQueue || isPlaying) { diff --git a/app/src/main/res/layout/all_episodes_fragment.xml b/app/src/main/res/layout/all_episodes_fragment.xml index 19db02f1d..87bf9266e 100644 --- a/app/src/main/res/layout/all_episodes_fragment.xml +++ b/app/src/main/res/layout/all_episodes_fragment.xml @@ -7,6 +7,15 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + - + +