diff options
34 files changed, 677 insertions, 953 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java index 973e4da2b..e7fbbcb89 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java @@ -34,6 +34,8 @@ import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.intent.Intents.intended; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; +import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; @@ -80,52 +82,51 @@ public class NavigationDrawerTest { uiTestUtils.addLocalFeedData(false); UserPreferences.setHiddenDrawerItems(new ArrayList<>()); activityRule.launchActivity(new Intent()); - MainActivity activity = activityRule.getActivity(); // queue openNavDrawer(); onDrawerItem(withText(R.string.queue_label)).perform(click()); - onView(isRoot()).perform(waitForView(withId(R.id.recyclerView), 1000)); - assertEquals(activity.getString(R.string.queue_label), activity.getSupportActionBar().getTitle()); + onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.toolbar)), + withText(R.string.queue_label)), 1000)); // episodes openNavDrawer(); onDrawerItem(withText(R.string.episodes_label)).perform(click()); - onView(isRoot()).perform(waitForView(withId(android.R.id.list), 1000)); - assertEquals(activity.getString(R.string.episodes_label), activity.getSupportActionBar().getTitle()); + onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.toolbar)), + withText(R.string.episodes_label), isDisplayed()), 1000)); // Subscriptions openNavDrawer(); onDrawerItem(withText(R.string.subscriptions_label)).perform(click()); - onView(isRoot()).perform(waitForView(withId(R.id.subscriptions_grid), 1000)); - assertEquals(activity.getString(R.string.subscriptions_label), activity.getSupportActionBar().getTitle()); + onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.toolbar)), + withText(R.string.subscriptions_label), isDisplayed()), 1000)); // downloads openNavDrawer(); onDrawerItem(withText(R.string.downloads_label)).perform(click()); - onView(isRoot()).perform(waitForView(withId(android.R.id.list), 1000)); - assertEquals(activity.getString(R.string.downloads_label), activity.getSupportActionBar().getTitle()); + onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.toolbar)), + withText(R.string.downloads_label), isDisplayed()), 1000)); // playback history openNavDrawer(); onDrawerItem(withText(R.string.playback_history_label)).perform(click()); - onView(isRoot()).perform(waitForView(withId(android.R.id.list), 1000)); - assertEquals(activity.getString(R.string.playback_history_label), activity.getSupportActionBar().getTitle()); + onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.toolbar)), + withText(R.string.playback_history_label), isDisplayed()), 1000)); // add podcast openNavDrawer(); onView(withId(R.id.nav_list)).perform(swipeUp()); onDrawerItem(withText(R.string.add_feed_label)).perform(click()); - onView(isRoot()).perform(waitForView(withId(R.id.btn_add_via_url), 1000)); - assertEquals(activity.getString(R.string.add_feed_label), activity.getSupportActionBar().getTitle()); + onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.toolbar)), + withText(R.string.add_feed_label), isDisplayed()), 1000)); // podcasts for (int i = 0; i < uiTestUtils.hostedFeeds.size(); i++) { Feed f = uiTestUtils.hostedFeeds.get(i); openNavDrawer(); onDrawerItem(withText(f.getTitle())).perform(scrollTo(), click()); - onView(isRoot()).perform(waitForView(withId(android.R.id.list), 1000)); - assertEquals("", activity.getSupportActionBar().getTitle()); + onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.appBar)), + withText(f.getTitle()), isDisplayed()), 1000)); } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java index 9206cebea..a827c4c04 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -28,7 +28,6 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.ThemeUtils; import de.danoeh.antennapod.view.viewholder.DownloadItemViewHolder; -import de.danoeh.antennapod.view.viewholder.FeedViewHolder; /** * Displays a list of DownloadStatus entries. diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java deleted file mode 100644 index b34963574..000000000 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java +++ /dev/null @@ -1,67 +0,0 @@ -package de.danoeh.antennapod.adapter; - -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.util.ThemeUtils; -import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; - -/** - * Shows a list of downloaded episodes. - */ -public class DownloadedEpisodesListAdapter extends BaseAdapter { - - private final MainActivity activity; - private final ItemAccess itemAccess; - - public DownloadedEpisodesListAdapter(MainActivity activity, ItemAccess itemAccess) { - super(); - this.activity = activity; - this.itemAccess = itemAccess; - } - - @Override - public int getCount() { - return itemAccess.getCount(); - } - - @Override - public FeedItem getItem(int position) { - return itemAccess.getItem(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - EpisodeItemViewHolder holder; - if (convertView == null) { - holder = new EpisodeItemViewHolder(activity, parent); - } else { - holder = (EpisodeItemViewHolder) convertView.getTag(); - } - - final FeedItem item = getItem(position); - holder.bind(item); - holder.dragHandle.setVisibility(View.GONE); - holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(activity, R.attr.ic_delete)); - holder.secondaryActionButton.setOnClickListener(v -> itemAccess.onFeedItemSecondaryAction(item)); - holder.hideSeparatorIfNecessary(); - - return holder.itemView; - } - - public interface ItemAccess { - int getCount(); - - FeedItem getItem(int position); - - void onFeedItemSecondaryAction(FeedItem item); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java index 71c872de2..f8fd99867 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java @@ -1,12 +1,12 @@ package de.danoeh.antennapod.adapter; +import android.app.Activity; import android.view.ContextMenu; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; @@ -24,17 +24,17 @@ import java.util.List; /** * List adapter for the list of new episodes. */ -public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<EpisodeItemViewHolder> +public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemViewHolder> implements View.OnCreateContextMenuListener { private final WeakReference<MainActivity> mainActivityRef; private List<FeedItem> episodes = new ArrayList<>(); - private FeedItem selectedItem; - public AllEpisodesRecycleAdapter(MainActivity mainActivity) { + public EpisodeItemListAdapter(MainActivity mainActivity) { super(); this.mainActivityRef = new WeakReference<>(mainActivity); + setHasStableIds(true); } public void updateItems(List<FeedItem> items) { @@ -45,9 +45,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<EpisodeItemV @NonNull @Override public EpisodeItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - EpisodeItemViewHolder viewHolder = new EpisodeItemViewHolder(mainActivityRef.get(), parent); - viewHolder.dragHandle.setVisibility(View.GONE); - return viewHolder; + return new EpisodeItemViewHolder(mainActivityRef.get(), parent); } @Override @@ -86,6 +84,14 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<EpisodeItemV return episodes.size(); } + protected FeedItem getItem(int index) { + return episodes.get(index); + } + + protected Activity getActivity() { + return mainActivityRef.get(); + } + @Override public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { MenuInflater inflater = mainActivityRef.get().getMenuInflater(); @@ -93,26 +99,4 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<EpisodeItemV menu.setHeaderTitle(selectedItem.getTitle()); FeedItemMenuHandler.onPrepareMenu(menu, selectedItem, R.id.skip_episode_item); } - - /** - * Notifies a View Holder of relevant callbacks from - * {@link ItemTouchHelper.Callback}. - */ - public interface ItemTouchHelperViewHolder { - - /** - * Called when the {@link ItemTouchHelper} first registers an - * item as being moved or swiped. - * Implementations should update the item view to indicate - * it's active state. - */ - void onItemSelected(); - - - /** - * Called when the {@link ItemTouchHelper} has completed the - * move or swipe, and the active item state should be cleared. - */ - void onItemClear(); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java deleted file mode 100644 index a5cfcb3e7..000000000 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java +++ /dev/null @@ -1,119 +0,0 @@ -package de.danoeh.antennapod.adapter; - -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ListView; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedComponent; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; -import de.danoeh.antennapod.view.viewholder.FeedComponentViewHolder; -import de.danoeh.antennapod.view.viewholder.FeedViewHolder; - -/** - * List adapter for items of feeds that the user has already subscribed to. - */ -public class FeedItemlistAdapter extends BaseAdapter { - - private final ItemAccess itemAccess; - private final MainActivity activity; - private final boolean makePlayedItemsTransparent; - private final boolean showIcons; - - private int currentlyPlayingItem = -1; - - public FeedItemlistAdapter(MainActivity activity, ItemAccess itemAccess, - boolean showIcons, boolean makePlayedItemsTransparent) { - super(); - this.activity = activity; - this.itemAccess = itemAccess; - this.showIcons = showIcons; - this.makePlayedItemsTransparent = makePlayedItemsTransparent; - } - - @Override - public int getCount() { - return itemAccess.getCount(); - - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public FeedComponent getItem(int position) { - return itemAccess.getItem(position); - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - final FeedComponent item = getItem(position); - if (item instanceof Feed) { - return getView((Feed) item, convertView, parent); - } else { - final FeedItem feeditem = (FeedItem) item; - if (feeditem.getMedia() != null && feeditem.getMedia().isCurrentlyPlaying()) { - currentlyPlayingItem = position; - } - return getView(feeditem, convertView, parent); - } - } - - private View getView(Feed item, View convertView, ViewGroup parent) { - FeedViewHolder holder; - if (convertView == null || !(convertView.getTag() instanceof FeedViewHolder)) { - holder = new FeedViewHolder(activity, parent); - } else { - holder = (FeedViewHolder) convertView.getTag(); - } - holder.bind(item); - return holder.itemView; - } - - private View getView(final FeedItem item, View convertView, ViewGroup parent) { - EpisodeItemViewHolder holder; - if (convertView == null || !(convertView.getTag() instanceof EpisodeItemViewHolder)) { - holder = new EpisodeItemViewHolder(activity, parent); - } else { - holder = (EpisodeItemViewHolder) convertView.getTag(); - } - - if (!showIcons) { - holder.coverHolder.setVisibility(View.GONE); - } - - holder.bind(item); - holder.dragHandle.setVisibility(View.GONE); - - if (!makePlayedItemsTransparent) { - holder.itemView.setAlpha(1.0f); - } - - holder.hideSeparatorIfNecessary(); - return holder.itemView; - } - - public void notifyCurrentlyPlayingItemChanged(PlaybackPositionEvent event, ListView listView) { - if (currentlyPlayingItem != -1 && currentlyPlayingItem < getCount()) { - View view = listView.getChildAt(currentlyPlayingItem - - listView.getFirstVisiblePosition() + listView.getHeaderViewsCount()); - if (view == null) { - return; - } - EpisodeItemViewHolder holder = (EpisodeItemViewHolder) view.getTag(); - holder.notifyPlaybackPositionUpdated(event); - } - } - - public interface ItemAccess { - int getCount(); - - FeedComponent getItem(int position); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java new file mode 100644 index 000000000..2e5ba31c9 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java @@ -0,0 +1,76 @@ +package de.danoeh.antennapod.adapter; + +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.fragment.FeedItemlistFragment; +import de.danoeh.antennapod.view.SquareImageView; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +public class FeedSearchResultAdapter extends RecyclerView.Adapter<FeedSearchResultAdapter.Holder> { + + private final WeakReference<MainActivity> mainActivityRef; + private final List<Feed> data = new ArrayList<>(); + + public FeedSearchResultAdapter(MainActivity mainActivity) { + this.mainActivityRef = new WeakReference<>(mainActivity); + } + + public void updateData(List<Feed> newData) { + data.clear(); + data.addAll(newData); + notifyDataSetChanged(); + } + + @NonNull + @Override + public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View convertView = View.inflate(mainActivityRef.get(), R.layout.searchlist_item_feed, null); + return new Holder(convertView); + } + + @Override + public void onBindViewHolder(@NonNull Holder holder, int position) { + final Feed podcast = data.get(position); + holder.imageView.setContentDescription(podcast.getTitle()); + holder.imageView.setOnClickListener(v -> + mainActivityRef.get().loadChildFragment(FeedItemlistFragment.newInstance(podcast.getId()))); + + Glide.with(mainActivityRef.get()) + .load(podcast.getImageUrl()) + .apply(new RequestOptions() + .placeholder(R.color.light_gray) + .fitCenter() + .dontAnimate()) + .into(holder.imageView); + } + + @Override + public long getItemId(int position) { + return data.get(position).getId(); + } + + @Override + public int getItemCount() { + return data.size(); + } + + static class Holder extends RecyclerView.ViewHolder { + SquareImageView imageView; + + public Holder(@NonNull View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.discovery_cover); + imageView.setDirection(SquareImageView.DIRECTION_HEIGHT); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java index 456f45fd2..14f537eb0 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -6,44 +6,24 @@ import android.view.ContextMenu; import android.view.MenuInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.core.view.MotionEventCompat; import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.LongList; -import de.danoeh.antennapod.fragment.ItemPagerFragment; -import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; -import org.apache.commons.lang3.ArrayUtils; - -import java.lang.ref.WeakReference; /** * List adapter for the queue. */ -public class QueueRecyclerAdapter extends RecyclerView.Adapter<EpisodeItemViewHolder> implements View.OnCreateContextMenuListener { +public class QueueRecyclerAdapter extends EpisodeItemListAdapter { private static final String TAG = "QueueRecyclerAdapter"; - private final WeakReference<MainActivity> mainActivity; - private final ItemAccess itemAccess; private final ItemTouchHelper itemTouchHelper; - private boolean locked; - private FeedItem selectedItem; - - public QueueRecyclerAdapter(MainActivity mainActivity, - ItemAccess itemAccess, - ItemTouchHelper itemTouchHelper) { - super(); - this.mainActivity = new WeakReference<>(mainActivity); - this.itemAccess = itemAccess; + public QueueRecyclerAdapter(MainActivity mainActivity, ItemTouchHelper itemTouchHelper) { + super(mainActivity); this.itemTouchHelper = itemTouchHelper; locked = UserPreferences.isQueueLocked(); } @@ -53,31 +33,10 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<EpisodeItemViewHo notifyDataSetChanged(); } - @NonNull - @Override - public EpisodeItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new EpisodeItemViewHolder(mainActivity.get(), parent); - } - @Override @SuppressLint("ClickableViewAccessibility") public void onBindViewHolder(EpisodeItemViewHolder holder, int pos) { - FeedItem item = itemAccess.getItem(pos); - holder.bind(item); - holder.dragHandle.setVisibility(locked ? View.GONE : View.VISIBLE); - holder.itemView.setOnLongClickListener(v -> { - selectedItem = item; - return false; - }); - holder.itemView.setOnClickListener(v -> { - MainActivity activity = mainActivity.get(); - if (activity != null) { - long[] ids = itemAccess.getQueueIds().toArray(); - int position = ArrayUtils.indexOf(ids, item.getId()); - activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position)); - } - }); - + super.onBindViewHolder(holder, pos); View.OnTouchListener startDragTouchListener = (v1, event) -> { if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { Log.d(TAG, "startDrag()"); @@ -85,80 +44,33 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<EpisodeItemViewHo } return false; }; - if (!locked) { - holder.dragHandle.setOnTouchListener(startDragTouchListener); - holder.coverHolder.setOnTouchListener(startDragTouchListener); - } else { + + if (locked) { + holder.dragHandle.setVisibility(View.GONE); holder.dragHandle.setOnTouchListener(null); holder.coverHolder.setOnTouchListener(null); + } else { + holder.dragHandle.setVisibility(View.VISIBLE); + holder.dragHandle.setOnTouchListener(startDragTouchListener); + holder.coverHolder.setOnTouchListener(startDragTouchListener); } - holder.itemView.setOnCreateContextMenuListener(this); holder.isInQueue.setVisibility(View.GONE); holder.hideSeparatorIfNecessary(); } - @Nullable - public FeedItem getSelectedItem() { - return selectedItem; - } - - @Override - public long getItemId(int position) { - FeedItem item = itemAccess.getItem(position); - return item != null ? item.getId() : RecyclerView.NO_POSITION; - } - - public int getItemCount() { - return itemAccess.getCount(); - } - @Override public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - MenuInflater inflater = mainActivity.get().getMenuInflater(); - inflater.inflate(R.menu.queue_context, menu); // queue-specific menu items - inflater.inflate(R.menu.feeditemlist_context, menu); // generic menu items for item feeds + MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.queue_context, menu); + super.onCreateContextMenu(menu, v, menuInfo); - menu.setHeaderTitle(selectedItem.getTitle()); - FeedItemMenuHandler.onPrepareMenu(menu, selectedItem, R.id.skip_episode_item); - // Queue-specific menu preparation final boolean keepSorted = UserPreferences.isQueueKeepSorted(); - final LongList queueAccess = itemAccess.getQueueIds(); - if (queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId() || keepSorted) { + if (getItem(0).getId() == getSelectedItem().getId() || keepSorted) { menu.findItem(R.id.move_to_top_item).setVisible(false); } - if (queueAccess.size() == 0 || queueAccess.get(queueAccess.size() - 1) == selectedItem.getId() || keepSorted) { + if (getItem(getItemCount() - 1).getId() == getSelectedItem().getId() || keepSorted) { menu.findItem(R.id.move_to_bottom_item).setVisible(false); } } - - public interface ItemAccess { - FeedItem getItem(int position); - - int getCount(); - - LongList getQueueIds(); - } - - /** - * Notifies a View Holder of relevant callbacks from - * {@link ItemTouchHelper.Callback}. - */ - public interface ItemTouchHelperViewHolder { - - /** - * Called when the {@link ItemTouchHelper} first registers an - * item as being moved or swiped. - * Implementations should update the item view to indicate - * it's active state. - */ - void onItemSelected(); - - - /** - * Called when the {@link ItemTouchHelper} has completed the - * move or swipe, and the active item state should be cleared. - */ - void onItemClear(); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index 101c5da27..fbd6a6670 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -1,61 +1,81 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.ListFragment; import android.util.Log; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.ListView; - -import java.util.ArrayList; -import java.util.List; - +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.DownloadedEpisodesListAdapter; +import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; +import de.danoeh.antennapod.adapter.actionbutton.DeleteActionButton; import de.danoeh.antennapod.core.event.DownloadLogEvent; +import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.event.PlayerStatusEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; +import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.view.EmptyViewHandler; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.ArrayList; +import java.util.List; import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_ADD_TO_QUEUE; import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE; /** - * Displays all running downloads and provides a button to delete them + * Displays all completed downloads and provides a button to delete them. */ -public class CompletedDownloadsFragment extends ListFragment { +public class CompletedDownloadsFragment extends Fragment { private static final String TAG = CompletedDownloadsFragment.class.getSimpleName(); private List<FeedItem> items = new ArrayList<>(); - private DownloadedEpisodesListAdapter listAdapter; + private CompletedDownloadsListAdapter adapter; + private RecyclerView recyclerView; private Disposable disposable; @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setHasOptionsMenu(true); - addVerticalPadding(); - addEmptyView(); + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.simple_list_fragment, container, false); + Toolbar toolbar = root.findViewById(R.id.toolbar); + toolbar.setVisibility(View.GONE); + + recyclerView = root.findViewById(R.id.recyclerView); + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setHasFixedSize(true); + recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build()); + recyclerView.setVisibility(View.GONE); + adapter = new CompletedDownloadsListAdapter((MainActivity) getActivity()); + recyclerView.setAdapter(adapter); - listAdapter = new DownloadedEpisodesListAdapter((MainActivity) getActivity(), itemAccess); - setListAdapter(listAdapter); - setListShown(false); + addEmptyView(); EventBus.getDefault().register(this); + return root; } @Override @@ -67,6 +87,7 @@ public class CompletedDownloadsFragment extends ListFragment { @Override public void onStart() { super.onStart(); + setHasOptionsMenu(true); loadItems(); } @@ -79,14 +100,6 @@ public class CompletedDownloadsFragment extends ListFragment { } @Override - public void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - position -= l.getHeaderViewsCount(); - long[] ids = FeedItemUtil.getIds(items); - ((MainActivity) requireActivity()).loadChildFragment(ItemPagerFragment.newInstance(ids, position)); - } - - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.downloads_completed, menu); @@ -103,41 +116,66 @@ public class CompletedDownloadsFragment extends ListFragment { return false; } + @Override + public boolean onContextItemSelected(@NonNull MenuItem item) { + FeedItem selectedItem = adapter.getSelectedItem(); + if (selectedItem == null) { + Log.i(TAG, "Selected item at current position was null, ignoring selection"); + return super.onContextItemSelected(item); + } + return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem); + } + private void addEmptyView() { EmptyViewHandler emptyView = new EmptyViewHandler(getActivity()); emptyView.setIcon(R.attr.av_download); emptyView.setTitle(R.string.no_comp_downloads_head_label); emptyView.setMessage(R.string.no_comp_downloads_label); - emptyView.attachToListView(getListView()); + emptyView.attachToRecyclerView(recyclerView); } - private void addVerticalPadding() { - final ListView lv = getListView(); - lv.setClipToPadding(false); - final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); - lv.setPadding(0, vertPadding, 0, vertPadding); - } - private final DownloadedEpisodesListAdapter.ItemAccess itemAccess = new DownloadedEpisodesListAdapter.ItemAccess() { - @Override - public int getCount() { - return items.size(); + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(FeedItemEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + if (items == null) { + return; + } else if (adapter == null) { + loadItems(); + return; } - - @Override - public FeedItem getItem(int position) { - if (0 <= position && position < items.size()) { - return items.get(position); - } else { - return null; + for (int i = 0, size = event.items.size(); i < size; i++) { + FeedItem item = event.items.get(i); + int pos = FeedItemUtil.indexOfItemWithId(items, item.getId()); + if (pos >= 0) { + items.remove(pos); + if (item.getMedia().isDownloaded()) { + items.add(pos, item); + adapter.notifyItemChanged(pos); + } else { + adapter.notifyItemRemoved(pos); + } } } + } - @Override - public void onFeedItemSecondaryAction(FeedItem item) { - DBWriter.deleteFeedMediaOfItem(requireActivity(), item.getMedia().getId()); + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (adapter != null) { + for (int i = 0; i < adapter.getItemCount(); i++) { + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i); + if (holder != null && holder.isCurrentlyPlayingItem()) { + holder.notifyPlaybackPositionUpdated(event); + break; + } + } } - }; + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerStatusChanged(PlayerStatusEvent event) { + loadItems(); + } @Subscribe public void onDownloadLogChanged(DownloadLogEvent event) { @@ -158,13 +196,22 @@ public class CompletedDownloadsFragment extends ListFragment { .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { items = result; - onItemsLoaded(); + adapter.updateItems(result); + requireActivity().invalidateOptionsMenu(); }, error -> Log.e(TAG, Log.getStackTraceString(error))); } - private void onItemsLoaded() { - setListShown(true); - listAdapter.notifyDataSetChanged(); - requireActivity().invalidateOptionsMenu(); + private static class CompletedDownloadsListAdapter extends EpisodeItemListAdapter { + + public CompletedDownloadsListAdapter(MainActivity mainActivity) { + super(mainActivity); + } + + @Override + public void onBindViewHolder(EpisodeItemViewHolder holder, int pos) { + super.onBindViewHolder(holder, pos); + DeleteActionButton actionButton = new DeleteActionButton(getItem(pos)); + actionButton.configure(holder.secondaryActionButton, holder.secondaryActionIcon, getActivity()); + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index f33b4e28f..cb72153c2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -6,10 +6,8 @@ import android.content.SharedPreferences; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; -import androidx.core.view.MenuItemCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import androidx.appcompat.widget.SearchView; import androidx.recyclerview.widget.SimpleItemAnimator; import android.util.Log; import android.view.LayoutInflater; @@ -24,6 +22,7 @@ import android.widget.Toast; import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; +import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.PlayerStatusEvent; @@ -38,14 +37,12 @@ import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; import de.danoeh.antennapod.core.event.FeedItemEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.service.download.DownloadService; -import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.FeedItemUtil; @@ -73,7 +70,7 @@ public abstract class EpisodesListFragment extends Fragment { protected int page = 1; RecyclerView recyclerView; - AllEpisodesRecycleAdapter listAdapter; + EpisodeItemListAdapter listAdapter; ProgressBar progLoading; View loadingMore; EmptyViewHandler emptyView; @@ -346,8 +343,7 @@ public abstract class EpisodesListFragment extends Fragment { */ private void createRecycleAdapter(RecyclerView recyclerView, EmptyViewHandler emptyViewHandler) { MainActivity mainActivity = (MainActivity) getActivity(); - listAdapter = new AllEpisodesRecycleAdapter(mainActivity); - listAdapter.setHasStableIds(true); + listAdapter = new EpisodeItemListAdapter(mainActivity); listAdapter.updateItems(episodes); recyclerView.setAdapter(listAdapter); emptyViewHandler.updateAdapter(listAdapter); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java index befe8757e..86b00a7ae 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -5,7 +5,6 @@ import android.content.DialogInterface; import android.graphics.LightingColorFilter; import android.os.Bundle; import android.util.Log; -import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -15,7 +14,6 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; @@ -23,19 +21,20 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; -import androidx.core.view.MenuItemCompat; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.widget.IconTextView; +import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.FeedItemlistAdapter; +import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.core.asynctask.FeedRemover; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; @@ -68,6 +67,7 @@ import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.FeedMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; import de.danoeh.antennapod.view.ToolbarIconTintManager; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -86,12 +86,11 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem private static final String TAG = "ItemlistFragment"; private static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id"; - private FeedItemlistAdapter adapter; - private AdapterView.AdapterContextMenuInfo lastMenuInfo = null; - private MoreContentListFooterUtil listFooter; + private FeedItemListAdapter adapter; + private MoreContentListFooterUtil nextPageLoader; private ProgressBar progressBar; - private ListView listView; + private RecyclerView recyclerView; private TextView txtvTitle; private IconTextView txtvFailure; private ImageView imgvBackground; @@ -144,9 +143,13 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem toolbar.setTitle(""); ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); - listView = root.findViewById(android.R.id.list); - listView.setOnItemClickListener(this); - registerForContextMenu(listView); + recyclerView = root.findViewById(R.id.recyclerView); + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setHasFixedSize(true); + recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build()); + recyclerView.setVisibility(View.GONE); + progressBar = root.findViewById(R.id.progLoading); txtvTitle = root.findViewById(R.id.txtvTitle); txtvAuthor = root.findViewById(R.id.txtvAuthor); @@ -176,6 +179,33 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } }; appBar.addOnOffsetChangedListener(iconTintManager); + + nextPageLoader = new MoreContentListFooterUtil(root.findViewById(R.id.more_content_list_footer)); + nextPageLoader.setClickListener(() -> { + if (feed != null) { + try { + DBTasks.loadNextPageOfFeed(getActivity(), feed, false); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage()); + } + } + }); + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int deltaX, int deltaY) { + super.onScrolled(recyclerView, deltaX, deltaY); + + int visibleEpisodeCount = recyclerView.getChildCount(); + int totalEpisodeCount = recyclerView.getLayoutManager().getItemCount(); + int firstVisibleEpisode = layoutManager.findFirstVisibleItemPosition(); + + boolean isAtBottom = (totalEpisodeCount - visibleEpisodeCount) <= (firstVisibleEpisode + 3); + boolean hasMorePages = feed != null && feed.isPaged() && feed.getNextPageLink() != null; + nextPageLoader.getRoot().setVisibility((isAtBottom && hasMorePages) ? View.VISIBLE : View.GONE); + } + }); + EventBus.getDefault().register(this); loadItems(); return root; @@ -190,7 +220,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem disposable.dispose(); } adapter = null; - listFooter = null; } private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() { @@ -283,35 +312,12 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; - - FeedItem item = (FeedItem) itemAccess.getItem(adapterInfo.position); - MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.feeditemlist_context, menu); - - if (item != null) { - menu.setHeaderTitle(item.getTitle()); - } - - lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; - FeedItemMenuHandler.onPrepareMenu(menu, item); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); - if (menuInfo == null) { - menuInfo = lastMenuInfo; - } - FeedItem selectedItem = feed.getItemAtIndex(menuInfo.position); - + public boolean onContextItemSelected(@NonNull MenuItem item) { + FeedItem selectedItem = adapter.getSelectedItem(); if (selectedItem == null) { - Log.i(TAG, "Selected item at position " + menuInfo.position + " was null, ignoring selection"); + Log.i(TAG, "Selected item at current position was null, ignoring selection"); return super.onContextItemSelected(item); } - return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem); } @@ -336,14 +342,19 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(FeedItemEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); - if (feed == null || feed.getItems() == null || adapter == null) { + if (feed == null || feed.getItems() == null) { + return; + } else if (adapter == null) { + loadItems(); return; } - for (FeedItem item : event.items) { + for (int i = 0, size = event.items.size(); i < size; i++) { + FeedItem item = event.items.get(i); int pos = FeedItemUtil.indexOfItemWithId(feed.getItems(), item.getId()); if (pos >= 0) { - loadItems(); - return; + feed.getItems().remove(pos); + feed.getItems().add(pos, item); + adapter.notifyItemChanged(pos); } } } @@ -356,14 +367,25 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem updateProgressBarVisibility(); } if (adapter != null && update.mediaIds.length > 0) { - adapter.notifyDataSetChanged(); + for (long mediaId : update.mediaIds) { + int pos = FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId); + if (pos >= 0) { + adapter.notifyItemChanged(pos); + } + } } } @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(PlaybackPositionEvent event) { if (adapter != null) { - adapter.notifyCurrentlyPlayingItemChanged(event, listView); + for (int i = 0; i < adapter.getItemCount(); i++) { + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i); + if (holder != null && holder.isCurrentlyPlayingItem()) { + holder.notifyPlaybackPositionUpdated(event); + break; + } + } } } @@ -394,9 +416,10 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem if (isUpdatingFeed != updateRefreshMenuItemChecker.isRefreshing()) { getActivity().supportInvalidateOptionsMenu(); } - if (listFooter != null) { - listFooter.setLoadingState(DownloadRequester.getInstance().isDownloadingFeeds()); + if (!DownloadRequester.getInstance().isDownloadingFeeds()) { + nextPageLoader.getRoot().setVisibility(View.GONE); } + nextPageLoader.setLoadingState(DownloadRequester.getInstance().isDownloadingFeeds()); } private void displayList() { @@ -405,24 +428,20 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem return; } if (adapter == null) { - listView.setAdapter(null); - setupFooterView(); - adapter = new FeedItemlistAdapter((MainActivity) getActivity(), itemAccess, false, true); - listView.setAdapter(adapter); + recyclerView.setAdapter(null); + adapter = new FeedItemListAdapter((MainActivity) getActivity()); + recyclerView.setAdapter(adapter); } - listView.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); - adapter.notifyDataSetChanged(); + adapter.updateItems(feed.getItems()); getActivity().supportInvalidateOptionsMenu(); - - if (feed != null && feed.getNextPageLink() == null && listFooter != null) { - listView.removeFooterView(listFooter.getRoot()); - } + updateProgressBarVisibility(); } private void refreshHeaderView() { - if (listView == null || feed == null || !headerCreated) { + if (recyclerView == null || feed == null || !headerCreated) { Log.e(TAG, "Unable to refresh header view"); return; } @@ -453,11 +472,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } private void setupHeaderView() { - if (listView == null || feed == null) { - Log.e(TAG, "Unable to setup listview: recyclerView = null or feed = null"); - return; - } - if (headerCreated) { + if (feed == null || headerCreated) { return; } @@ -504,49 +519,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem .into(imgvCover); } - - private void setupFooterView() { - if (listView == null || feed == null) { - Log.e(TAG, "Unable to setup listview: recyclerView = null or feed = null"); - return; - } - if (feed.isPaged() && feed.getNextPageLink() != null) { - LayoutInflater inflater = (LayoutInflater) - getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View header = inflater.inflate(R.layout.more_content_list_footer, listView, false); - listView.addFooterView(header); - listFooter = new MoreContentListFooterUtil(header); - listFooter.setClickListener(() -> { - if (feed != null) { - try { - DBTasks.loadNextPageOfFeed(getActivity(), feed, false); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage()); - } - } - }); - } - } - - private final FeedItemlistAdapter.ItemAccess itemAccess = new FeedItemlistAdapter.ItemAccess() { - - @Override - public FeedItem getItem(int position) { - if (feed != null && 0 <= position && position < feed.getNumOfItems()) { - return feed.getItemAtIndex(position); - } else { - return null; - } - } - - @Override - public int getCount() { - return (feed != null) ? feed.getNumOfItems() : 0; - } - - }; - private void loadItems() { if (disposable != null) { disposable.dispose(); @@ -576,4 +548,18 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } return Optional.ofNullable(feed); } + + private static class FeedItemListAdapter extends EpisodeItemListAdapter { + public FeedItemListAdapter(MainActivity mainActivity) { + super(mainActivity); + } + + @NonNull + @Override + public EpisodeItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + EpisodeItemViewHolder viewHolder = super.onCreateViewHolder(parent, viewType); + viewHolder.coverHolder.setVisibility(View.GONE); + return viewHolder; + } + } } 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 94f71894b..4577aed23 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -12,7 +12,6 @@ import android.view.ViewGroup; import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; @@ -53,7 +52,8 @@ public class NewEpisodesFragment extends EpisodesListFragment { ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) { @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, + RecyclerView.ViewHolder target) { return false; } @@ -62,33 +62,6 @@ public class NewEpisodesFragment extends EpisodesListFragment { EpisodeItemViewHolder holder = (EpisodeItemViewHolder) viewHolder; FeedItemMenuHandler.removeNewFlagWithUndo(NewEpisodesFragment.this, holder.getFeedItem()); } - - @Override - public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, - int actionState) { - // We only want the active item - if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { - if (viewHolder instanceof AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) { - AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder itemViewHolder = - (AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) viewHolder; - itemViewHolder.onItemSelected(); - } - } - - super.onSelectedChanged(viewHolder, actionState); - } - - @Override - public void clearView(RecyclerView recyclerView, - RecyclerView.ViewHolder viewHolder) { - super.clearView(recyclerView, viewHolder); - - if (viewHolder instanceof AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) { - AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder itemViewHolder = - (AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) viewHolder; - itemViewHolder.onItemClear(); - } - } }; ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index f9bc6642e..36cda5c84 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -9,26 +9,31 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.view.MenuItemCompat; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.FeedItemlistAdapter; +import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.DownloaderUpdate; import de.danoeh.antennapod.core.event.FeedItemEvent; import de.danoeh.antennapod.core.event.PlaybackHistoryEvent; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.PlayerStatusEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.FeedItemUtil; +import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.view.EmptyViewHandler; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -39,13 +44,13 @@ import org.greenrobot.eventbus.ThreadMode; import java.util.List; -public class PlaybackHistoryFragment extends Fragment implements AdapterView.OnItemClickListener { +public class PlaybackHistoryFragment extends Fragment { public static final String TAG = "PlaybackHistoryFragment"; private List<FeedItem> playbackHistory; - private FeedItemlistAdapter adapter; + private PlaybackHistoryListAdapter adapter; private Disposable disposable; - private ListView listView; + private RecyclerView recyclerView; @Override public void onCreate(Bundle savedInstanceState) { @@ -62,18 +67,20 @@ public class PlaybackHistoryFragment extends Fragment implements AdapterView.OnI toolbar.setTitle(R.string.playback_history_label); ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); - listView = root.findViewById(android.R.id.list); + recyclerView = root.findViewById(R.id.recyclerView); + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setHasFixedSize(true); + recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build()); + recyclerView.setVisibility(View.GONE); + adapter = new PlaybackHistoryListAdapter((MainActivity) getActivity()); + recyclerView.setAdapter(adapter); + EmptyViewHandler emptyView = new EmptyViewHandler(getActivity()); emptyView.setIcon(R.attr.ic_history); emptyView.setTitle(R.string.no_history_head_label); emptyView.setMessage(R.string.no_history_label); - emptyView.attachToListView(listView); - - // played items shoudln't be transparent for this fragment since, *all* items - // in this fragment will, by definition, be played. So it serves no purpose and can make - // it harder to read. - adapter = new FeedItemlistAdapter((MainActivity) getActivity(), itemAccess, true, false); - listView.setAdapter(adapter); + emptyView.attachToRecyclerView(recyclerView); return root; } @@ -93,17 +100,51 @@ public class PlaybackHistoryFragment extends Fragment implements AdapterView.OnI } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(FeedItemEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + if (playbackHistory == null) { + return; + } else if (adapter == null) { + loadItems(); + return; + } + for (int i = 0, size = event.items.size(); i < size; i++) { + FeedItem item = event.items.get(i); + int pos = FeedItemUtil.indexOfItemWithId(playbackHistory, item.getId()); + if (pos >= 0) { + playbackHistory.remove(pos); + playbackHistory.add(pos, item); + adapter.notifyItemChanged(pos); + } + } + } + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) - public void onEvent(DownloadEvent event) { - Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); - adapter.notifyDataSetChanged(); + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + DownloaderUpdate update = event.update; + if (adapter != null && update.mediaIds.length > 0) { + for (long mediaId : update.mediaIds) { + int pos = FeedItemUtil.indexOfItemWithMediaId(playbackHistory, mediaId); + if (pos >= 0) { + adapter.notifyItemChanged(pos); + } + } + } } - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - position -= listView.getHeaderViewsCount(); - long[] ids = FeedItemUtil.getIds(playbackHistory); - ((MainActivity) getActivity()).loadChildFragment(ItemPagerFragment.newInstance(ids, position)); + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (adapter != null) { + for (int i = 0; i < adapter.getItemCount(); i++) { + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i); + if (holder != null && holder.isCurrentlyPlayingItem()) { + holder.notifyPlaybackPositionUpdated(event); + break; + } + } + } } @Override @@ -143,19 +184,14 @@ public class PlaybackHistoryFragment extends Fragment implements AdapterView.OnI } } - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEventMainThread(FeedItemEvent event) { - Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); - if (playbackHistory == null) { - return; - } - for (FeedItem item : event.items) { - int pos = FeedItemUtil.indexOfItemWithId(playbackHistory, item.getId()); - if (pos >= 0) { - loadItems(); - return; - } + @Override + public boolean onContextItemSelected(@NonNull MenuItem item) { + FeedItem selectedItem = adapter.getSelectedItem(); + if (selectedItem == null) { + Log.i(TAG, "Selected item at current position was null, ignoring selection"); + return super.onContextItemSelected(item); } + return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem); } @Subscribe(threadMode = ThreadMode.MAIN) @@ -175,23 +211,6 @@ public class PlaybackHistoryFragment extends Fragment implements AdapterView.OnI getActivity().supportInvalidateOptionsMenu(); } - private final FeedItemlistAdapter.ItemAccess itemAccess = new FeedItemlistAdapter.ItemAccess() { - - @Override - public int getCount() { - return (playbackHistory != null) ? playbackHistory.size() : 0; - } - - @Override - public FeedItem getItem(int position) { - if (playbackHistory != null && 0 <= position && position < playbackHistory.size()) { - return playbackHistory.get(position); - } else { - return null; - } - } - }; - private void loadItems() { if (disposable != null) { disposable.dispose(); @@ -202,6 +221,7 @@ public class PlaybackHistoryFragment extends Fragment implements AdapterView.OnI .subscribe(result -> { if (result != null) { playbackHistory = result; + adapter.updateItems(playbackHistory); onFragmentLoaded(); } }, error -> Log.e(TAG, Log.getStackTraceString(error))); @@ -213,4 +233,20 @@ public class PlaybackHistoryFragment extends Fragment implements AdapterView.OnI DBReader.loadAdditionalFeedItemListData(history); return history; } + + private static class PlaybackHistoryListAdapter extends EpisodeItemListAdapter { + + public PlaybackHistoryListAdapter(MainActivity mainActivity) { + super(mainActivity); + } + + @Override + public void onBindViewHolder(EpisodeItemViewHolder holder, int pos) { + super.onBindViewHolder(holder, pos); + // played items shouldn't be transparent for this fragment since, *all* items + // in this fragment will, by definition, be played. So it serves no purpose and can make + // it harder to read. + holder.itemView.setAlpha(1.0f); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index b038a7ad1..404ea1d8d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -14,27 +14,15 @@ import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.ProgressBar; import android.widget.TextView; - import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.SearchView; -import androidx.core.view.MenuItemCompat; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.SimpleItemAnimator; - import com.google.android.material.snackbar.Snackbar; import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; - -import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.util.List; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.QueueRecyclerAdapter; @@ -44,8 +32,8 @@ import de.danoeh.antennapod.core.event.DownloaderUpdate; import de.danoeh.antennapod.core.event.FeedItemEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.event.QueueEvent; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -55,24 +43,29 @@ import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.FeedItemUtil; -import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.SortOrder; import de.danoeh.antennapod.core.util.download.AutoUpdateManager; import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; import de.danoeh.antennapod.view.EmptyViewHandler; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.List; import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE; -import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REMOVE_FROM_QUEUE; import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DOWNLOAD; +import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REMOVE_FROM_QUEUE; /** - * Shows all items in the queue + * Shows all items in the queue. */ public class QueueFragment extends Fragment { public static final String TAG = "QueueFragment"; @@ -179,10 +172,10 @@ public class QueueFragment extends Fragment { loadItems(true); return; } - for(int i=0, size = event.items.size(); i < size; i++) { + for (int i = 0, size = event.items.size(); i < size; i++) { FeedItem item = event.items.get(i); int pos = FeedItemUtil.indexOfItemWithId(queue, item.getId()); - if(pos >= 0) { + if (pos >= 0) { queue.remove(pos); queue.add(pos, item); recyclerAdapter.notifyItemChanged(pos); @@ -501,12 +494,13 @@ public class QueueFragment extends Fragment { int dragTo = -1; @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, + RecyclerView.ViewHolder target) { int fromPosition = viewHolder.getAdapterPosition(); int toPosition = target.getAdapterPosition(); // Update tracked position - if(dragFrom == -1) { + if (dragFrom == -1) { dragFrom = fromPosition; } dragTo = toPosition; @@ -514,7 +508,7 @@ public class QueueFragment extends Fragment { int from = viewHolder.getAdapterPosition(); int to = target.getAdapterPosition(); Log.d(TAG, "move(" + from + ", " + to + ") in memory"); - if(from >= queue.size() || to >= queue.size()) { + if (from >= queue.size() || to >= queue.size()) { return false; } queue.add(to, queue.remove(from)); @@ -524,7 +518,7 @@ public class QueueFragment extends Fragment { @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } final int position = viewHolder.getAdapterPosition(); @@ -556,36 +550,14 @@ public class QueueFragment extends Fragment { } @Override - public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, - int actionState) { - // We only want the active item - if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { - if (viewHolder instanceof QueueRecyclerAdapter.ItemTouchHelperViewHolder) { - QueueRecyclerAdapter.ItemTouchHelperViewHolder itemViewHolder = - (QueueRecyclerAdapter.ItemTouchHelperViewHolder) viewHolder; - itemViewHolder.onItemSelected(); - } - } - - super.onSelectedChanged(viewHolder, actionState); - } - @Override - public void clearView(RecyclerView recyclerView, - RecyclerView.ViewHolder viewHolder) { + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); - // Check if drag finished - if(dragFrom != -1 && dragTo != -1 && dragFrom != dragTo) { + if (dragFrom != -1 && dragTo != -1 && dragFrom != dragTo) { reallyMoved(dragFrom, dragTo); } dragFrom = dragTo = -1; - - if (viewHolder instanceof QueueRecyclerAdapter.ItemTouchHelperViewHolder) { - QueueRecyclerAdapter.ItemTouchHelperViewHolder itemViewHolder = - (QueueRecyclerAdapter.ItemTouchHelperViewHolder) viewHolder; - itemViewHolder.onItemClear(); - } } private void reallyMoved(int from, int to) { @@ -613,11 +585,11 @@ public class QueueFragment extends Fragment { if (queue != null && queue.size() > 0) { if (recyclerAdapter == null) { MainActivity activity = (MainActivity) getActivity(); - recyclerAdapter = new QueueRecyclerAdapter(activity, itemAccess, itemTouchHelper); - recyclerAdapter.setHasStableIds(true); + recyclerAdapter = new QueueRecyclerAdapter(activity, itemTouchHelper); recyclerView.setAdapter(recyclerAdapter); emptyView.updateAdapter(recyclerAdapter); } + recyclerAdapter.updateItems(queue); recyclerView.setVisibility(View.VISIBLE); } else { recyclerAdapter = null; @@ -657,29 +629,9 @@ public class QueueFragment extends Fragment { infoBar.setText(info); } - private final QueueRecyclerAdapter.ItemAccess itemAccess = new QueueRecyclerAdapter.ItemAccess() { - @Override - public int getCount() { - return queue != null ? queue.size() : 0; - } - - @Override - public FeedItem getItem(int position) { - if (queue != null && 0 <= position && position < queue.size()) { - return queue.get(position); - } - return null; - } - - @Override - public LongList getQueueIds() { - return queue != null ? LongList.of(FeedItemUtil.getIds(queue)) : new LongList(0); - } - }; - private void loadItems(final boolean restoreScrollPosition) { Log.d(TAG, "loadItems()"); - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } if (queue == null) { @@ -694,7 +646,7 @@ public class QueueFragment extends Fragment { progLoading.setVisibility(View.GONE); queue = items; onFragmentLoaded(restoreScrollPosition); - if(recyclerAdapter != null) { + if (recyclerAdapter != null) { recyclerAdapter.notifyDataSetChanged(); } }, error -> Log.e(TAG, Log.getStackTraceString(error))); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index 463df92bb..4bb7ec28a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -3,55 +3,65 @@ package de.danoeh.antennapod.fragment; import android.content.Context; import android.os.Bundle; import android.util.Log; +import android.util.Pair; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SearchView; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.FeedItemlistAdapter; +import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; +import de.danoeh.antennapod.adapter.FeedSearchResultAdapter; import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.DownloaderUpdate; +import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.event.PlayerStatusEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedComponent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.FeedSearcher; +import de.danoeh.antennapod.core.util.FeedItemUtil; +import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.view.EmptyViewHandler; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; -import java.util.List; - import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import java.util.List; + /** * Performs a search operation on all feeds or one specific feed and displays the search result. */ -public class SearchFragment extends Fragment implements AdapterView.OnItemClickListener { +public class SearchFragment extends Fragment { private static final String TAG = "SearchFragment"; - private static final String ARG_QUERY = "query"; private static final String ARG_FEED = "feed"; - private FeedItemlistAdapter searchAdapter; - private List<FeedComponent> searchResults = new ArrayList<>(); + private EpisodeItemListAdapter adapter; + private FeedSearchResultAdapter adapterFeeds; private Disposable disposable; private ProgressBar progressBar; private EmptyViewHandler emptyViewHandler; + private RecyclerView recyclerView; + private RecyclerView recyclerViewFeeds; + private List<FeedItem> results; /** * Create a new SearchFragment that searches all feeds. @@ -104,14 +114,27 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL @Nullable Bundle savedInstanceState) { View layout = inflater.inflate(R.layout.search_fragment, container, false); ((AppCompatActivity) getActivity()).setSupportActionBar(layout.findViewById(R.id.toolbar)); - ListView listView = layout.findViewById(R.id.listview); progressBar = layout.findViewById(R.id.progressBar); - searchAdapter = new FeedItemlistAdapter((MainActivity) getActivity(), itemAccess, true, true); - listView.setAdapter(searchAdapter); - listView.setOnItemClickListener(this); + + recyclerView = layout.findViewById(R.id.recyclerView); + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setHasFixedSize(true); + recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build()); + recyclerView.setVisibility(View.GONE); + adapter = new EpisodeItemListAdapter((MainActivity) getActivity()); + recyclerView.setAdapter(adapter); + + recyclerViewFeeds = layout.findViewById(R.id.recyclerViewFeeds); + LinearLayoutManager layoutManagerFeeds = new LinearLayoutManager(getActivity()); + layoutManagerFeeds.setOrientation(RecyclerView.HORIZONTAL); + recyclerViewFeeds.setLayoutManager(layoutManagerFeeds); + recyclerViewFeeds.setHasFixedSize(true); + adapterFeeds = new FeedSearchResultAdapter((MainActivity) getActivity()); + recyclerViewFeeds.setAdapter(adapterFeeds); emptyViewHandler = new EmptyViewHandler(getContext()); - emptyViewHandler.attachToListView(listView); + emptyViewHandler.attachToRecyclerView(recyclerView); emptyViewHandler.setIcon(R.attr.action_search); emptyViewHandler.setTitle(R.string.search_status_no_results); EventBus.getDefault().register(this); @@ -125,17 +148,6 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - FeedComponent comp = searchAdapter.getItem(position); - if (comp.getClass() == Feed.class) { - ((MainActivity) getActivity()).loadFeedFragmentById(comp.getId(), null); - } else if (comp.getClass() == FeedItem.class) { - FeedItem item = (FeedItem) comp; - ((MainActivity) getActivity()).loadChildFragment(ItemPagerFragment.newInstance(item.getId())); - } - } - - @Override public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.search, menu); @@ -173,42 +185,72 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL }); } + @Override + public boolean onContextItemSelected(@NonNull MenuItem item) { + FeedItem selectedItem = adapter.getSelectedItem(); + if (selectedItem == null) { + Log.i(TAG, "Selected item at current position was null, ignoring selection"); + return super.onContextItemSelected(item); + } + return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem); + } + @Subscribe public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { search(); } - @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) - public void onEventMainThread(DownloadEvent event) { + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(FeedItemEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); - if (searchAdapter != null) { - searchAdapter.notifyDataSetChanged(); + if (results == null) { + return; + } else if (adapter == null) { + search(); + return; + } + for (int i = 0, size = event.items.size(); i < size; i++) { + FeedItem item = event.items.get(i); + int pos = FeedItemUtil.indexOfItemWithId(results, item.getId()); + if (pos >= 0) { + results.remove(pos); + results.add(pos, item); + adapter.notifyItemChanged(pos); + } } } - private void onSearchResults(List<FeedComponent> results) { - progressBar.setVisibility(View.GONE); - searchResults = results; - searchAdapter.notifyDataSetChanged(); - String query = getArguments().getString(ARG_QUERY); - emptyViewHandler.setMessage(getString(R.string.no_results_for_query, query)); - } - - private final FeedItemlistAdapter.ItemAccess itemAccess = new FeedItemlistAdapter.ItemAccess() { - @Override - public int getCount() { - return searchResults.size(); + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + DownloaderUpdate update = event.update; + if (adapter != null && update.mediaIds.length > 0) { + for (long mediaId : update.mediaIds) { + int pos = FeedItemUtil.indexOfItemWithMediaId(results, mediaId); + if (pos >= 0) { + adapter.notifyItemChanged(pos); + } + } } + } - @Override - public FeedComponent getItem(int position) { - if (0 <= position && position < searchResults.size()) { - return searchResults.get(position); - } else { - return null; + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (adapter != null) { + for (int i = 0; i < adapter.getItemCount(); i++) { + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i); + if (holder != null && holder.isCurrentlyPlayingItem()) { + holder.notifyPlaybackPositionUpdated(event); + break; + } } } - }; + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerStatusChanged(PlayerStatusEvent event) { + search(); + } private void search() { if (disposable != null) { @@ -219,15 +261,22 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL disposable = Observable.fromCallable(this::performSearch) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::onSearchResults, error -> Log.e(TAG, Log.getStackTraceString(error))); + .subscribe(results -> { + progressBar.setVisibility(View.GONE); + this.results = results.first; + adapter.updateItems(results.first); + adapterFeeds.updateData(results.second); + String query = getArguments().getString(ARG_QUERY); + emptyViewHandler.setMessage(getString(R.string.no_results_for_query, query)); + }, error -> Log.e(TAG, Log.getStackTraceString(error))); } @NonNull - private List<FeedComponent> performSearch() { - Bundle args = getArguments(); - String query = args.getString(ARG_QUERY); - long feed = args.getLong(ARG_FEED); - Context context = getActivity(); - return FeedSearcher.performSearch(context, query, feed); + private Pair<List<FeedItem>, List<Feed>> performSearch() { + String query = getArguments().getString(ARG_QUERY); + long feed = getArguments().getLong(ARG_FEED); + List<FeedItem> items = FeedSearcher.searchFeedItems(getContext(), query, feed); + List<Feed> feeds = FeedSearcher.searchFeeds(getContext(), query); + return new Pair<>(items, feeds); } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java index c6b50e728..f41b036b2 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java +++ b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java @@ -78,8 +78,13 @@ public class CircularProgressBar extends View { } if (Math.abs(percentage - targetPercentage) > EPSILON) { - float delta = Math.min(0.02f, Math.abs(targetPercentage - percentage)); - percentage += delta * ((targetPercentage - percentage) > 0 ? 1f : -1f); + float speed = 0.02f; + if (Math.abs(targetPercentage - percentage) < 0.1 && targetPercentage > percentage) { + speed = 0.006f; + } + float delta = Math.min(speed, Math.abs(targetPercentage - percentage)); + float direction = ((targetPercentage - percentage) > 0 ? 1f : -1f); + percentage += delta * direction; invalidate(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/NestedScrollingListView.java b/app/src/main/java/de/danoeh/antennapod/view/NestedScrollingListView.java deleted file mode 100644 index e7a6eefc2..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/NestedScrollingListView.java +++ /dev/null @@ -1,74 +0,0 @@ -package de.danoeh.antennapod.view; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.ListView; -import androidx.core.view.NestedScrollingChild; -import androidx.core.view.NestedScrollingChildHelper; - -/** - * ListView that can be wrapped in NestedScrollView - * Based on https://stackoverflow.com/a/34920961. - */ -public class NestedScrollingListView extends ListView implements NestedScrollingChild { - private final NestedScrollingChildHelper nestedScrollingChildHelper; - - public NestedScrollingListView(Context context) { - super(context); - nestedScrollingChildHelper = new NestedScrollingChildHelper(this); - setNestedScrollingEnabled(true); - } - - public NestedScrollingListView(Context context, AttributeSet attrs) { - super(context, attrs); - nestedScrollingChildHelper = new NestedScrollingChildHelper(this); - setNestedScrollingEnabled(true); - } - - @Override - public void setNestedScrollingEnabled(boolean enabled) { - nestedScrollingChildHelper.setNestedScrollingEnabled(enabled); - } - - @Override - public boolean isNestedScrollingEnabled() { - return nestedScrollingChildHelper.isNestedScrollingEnabled(); - } - - @Override - public boolean startNestedScroll(int axes) { - return nestedScrollingChildHelper.startNestedScroll(axes); - } - - @Override - public void stopNestedScroll() { - nestedScrollingChildHelper.stopNestedScroll(); - } - - @Override - public boolean hasNestedScrollingParent() { - return nestedScrollingChildHelper.hasNestedScrollingParent(); - } - - @Override - public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, - int dyUnconsumed, int[] offsetInWindow) { - return nestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, - dxUnconsumed, dyUnconsumed, offsetInWindow); - } - - @Override - public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { - return nestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); - } - - @Override - public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { - return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); - } - - @Override - public boolean dispatchNestedPreFling(float velocityX, float velocityY) { - return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java index dcf1edbe7..c256ede9e 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java +++ b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java @@ -10,7 +10,11 @@ import de.danoeh.antennapod.core.R; * From http://stackoverflow.com/a/19449488/6839 */ public class SquareImageView extends AppCompatImageView { - private boolean useMinimum = false; + public static final int DIRECTION_WIDTH = 0; + public static final int DIRECTION_HEIGHT = 1; + public static final int DIRECTION_MINIMUM = 2; + + private int direction = DIRECTION_WIDTH; public SquareImageView(Context context) { super(context); @@ -27,20 +31,32 @@ public class SquareImageView extends AppCompatImageView { } private void loadAttrs(Context context, AttributeSet attrs) { - TypedArray a = context.obtainStyledAttributes(attrs, new int[]{R.styleable.SquareImageView_useMinimum}); - useMinimum = a.getBoolean(0, false); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SquareImageView); + direction = a.getInt(R.styleable.SquareImageView_direction, DIRECTION_WIDTH); a.recycle(); } + public void setDirection(int direction) { + this.direction = direction; + requestLayout(); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int size = getMeasuredWidth(); - if (useMinimum) { - size = Math.min(getMeasuredWidth(), getMeasuredHeight()); + switch (direction) { + case DIRECTION_MINIMUM: + int size = Math.min(getMeasuredWidth(), getMeasuredHeight()); + setMeasuredDimension(size, size); + break; + case DIRECTION_HEIGHT: + setMeasuredDimension(getMeasuredHeight(), getMeasuredHeight()); + break; + default: + setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); + break; } - setMeasuredDimension(size, size); } }
\ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java index f9c02524e..a6300b50e 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java @@ -1,16 +1,13 @@ package de.danoeh.antennapod.view.viewholder; -import android.graphics.Color; import android.os.Build; import android.text.Layout; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; -import android.widget.RelativeLayout; import android.widget.TextView; import androidx.cardview.widget.CardView; import androidx.recyclerview.widget.RecyclerView; @@ -36,8 +33,7 @@ import de.danoeh.antennapod.view.CircularProgressBar; /** * Holds the view which shows FeedItems. */ -public class EpisodeItemViewHolder extends FeedComponentViewHolder - implements QueueRecyclerAdapter.ItemTouchHelperViewHolder { +public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { private static final String TAG = "EpisodeItemViewHolder"; private final View container; @@ -91,16 +87,6 @@ public class EpisodeItemViewHolder extends FeedComponentViewHolder itemView.setTag(this); } - @Override - public void onItemSelected() { - itemView.setAlpha(0.5f); - } - - @Override - public void onItemClear() { - itemView.setAlpha(1.0f); - } - public void bind(FeedItem item) { this.item = item; placeholder.setText(item.getFeed().getTitle()); diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java deleted file mode 100644 index f55ea9bc8..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.danoeh.antennapod.view.viewholder; - -import android.view.View; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -/** - * Holds the view which shows FeedComponents. - */ -public class FeedComponentViewHolder extends RecyclerView.ViewHolder { - - public FeedComponentViewHolder(@NonNull View itemView) { - super(itemView); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java deleted file mode 100644 index 83250bbfa..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java +++ /dev/null @@ -1,62 +0,0 @@ -package de.danoeh.antennapod.view.viewholder; - -import android.os.Build; -import android.text.Layout; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import androidx.cardview.widget.CardView; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.CoverLoader; -import de.danoeh.antennapod.core.feed.Feed; - -/** - * Holds the view which shows feeds. - */ -public class FeedViewHolder extends FeedComponentViewHolder { - private static final String TAG = "FeedViewHolder"; - - private final TextView placeholder; - private final ImageView cover; - private final TextView title; - public final CardView coverHolder; - - private final MainActivity activity; - private Feed feed; - - public FeedViewHolder(MainActivity activity, ViewGroup parent) { - super(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false)); - this.activity = activity; - placeholder = itemView.findViewById(R.id.txtvPlaceholder); - cover = itemView.findViewById(R.id.imgvCover); - coverHolder = itemView.findViewById(R.id.coverHolder); - title = itemView.findViewById(R.id.txtvTitle); - if (Build.VERSION.SDK_INT >= 23) { - title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); - } - - itemView.findViewById(R.id.secondaryActionButton).setVisibility(View.GONE); - itemView.findViewById(R.id.status).setVisibility(View.GONE); - itemView.findViewById(R.id.progress).setVisibility(View.GONE); - itemView.findViewById(R.id.drag_handle).setVisibility(View.GONE); - itemView.setTag(this); - } - - public void bind(Feed feed) { - this.feed = feed; - placeholder.setText(feed.getTitle()); - title.setText(feed.getTitle()); - - if (coverHolder.getVisibility() == View.VISIBLE) { - new CoverLoader(activity) - .withUri(feed.getImageLocation()) - .withPlaceholderView(placeholder) - .withCoverView(cover) - .load(); - } - } - -} diff --git a/app/src/main/res/layout/addfeed.xml b/app/src/main/res/layout/addfeed.xml index 3b9c0ffb4..9d14d209a 100644 --- a/app/src/main/res/layout/addfeed.xml +++ b/app/src/main/res/layout/addfeed.xml @@ -2,7 +2,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_height="match_parent" android:layout_width="match_parent" + android:layout_width="match_parent" + android:layout_height="match_parent" android:orientation="vertical"> <androidx.appcompat.widget.Toolbar @@ -11,6 +12,7 @@ android:minHeight="?attr/actionBarSize" android:theme="?attr/actionBarTheme" app:title="@string/add_feed_label" + app:navigationIcon="?homeAsUpIndicator" android:id="@+id/toolbar"/> <androidx.cardview.widget.CardView @@ -19,8 +21,7 @@ app:cardCornerRadius="4dp" app:cardElevation="4dp" android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" - android:layout_marginTop="16dp"> + android:layout_marginRight="16dp"> <LinearLayout android:layout_width="match_parent" diff --git a/app/src/main/res/layout/cover_fragment.xml b/app/src/main/res/layout/cover_fragment.xml index 4292344fd..31e7dbda7 100644 --- a/app/src/main/res/layout/cover_fragment.xml +++ b/app/src/main/res/layout/cover_fragment.xml @@ -20,7 +20,7 @@ android:transitionName="coverTransition" tools:src="@android:drawable/sym_def_app_icon" android:foreground="?attr/selectableItemBackgroundBorderless" - squareImageView:useMinimum="true" /> + squareImageView:direction="minimum" /> <TextView android:id="@+id/txtvPodcastTitle" diff --git a/app/src/main/res/layout/feed_item_list_fragment.xml b/app/src/main/res/layout/feed_item_list_fragment.xml index f2aa395de..2c5c30ed3 100644 --- a/app/src/main/res/layout/feed_item_list_fragment.xml +++ b/app/src/main/res/layout/feed_item_list_fragment.xml @@ -39,17 +39,17 @@ android:theme="?attr/actionBarTheme" android:layout_alignParentTop="true" android:id="@+id/toolbar" + app:navigationIcon="?homeAsUpIndicator" app:layout_collapseMode="pin"/> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> - <de.danoeh.antennapod.view.NestedScrollingListView - android:clipToPadding="false" - app:layout_behavior="@string/appbar_scrolling_view_behavior" + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@android:id/list" /> + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <ProgressBar android:id="@+id/progLoading" @@ -59,4 +59,11 @@ android:indeterminateOnly="true" android:visibility="gone"/> + <include + layout="@layout/more_content_list_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:visibility="gone"/> + </androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/app/src/main/res/layout/feeditemlist_item.xml b/app/src/main/res/layout/feeditemlist_item.xml index 1bc18dc0a..66febc1d0 100644 --- a/app/src/main/res/layout/feeditemlist_item.xml +++ b/app/src/main/res/layout/feeditemlist_item.xml @@ -25,6 +25,7 @@ android:src="?attr/dragview_background" android:paddingEnd="8dp" android:paddingRight="8dp" + android:visibility="gone" tools:src="@drawable/ic_drag_vertical_grey600_48dp" tools:background="@android:color/holo_green_dark"/> diff --git a/app/src/main/res/layout/quick_feed_discovery_item.xml b/app/src/main/res/layout/quick_feed_discovery_item.xml index 3a0fe0b4b..e1c91f9d8 100644 --- a/app/src/main/res/layout/quick_feed_discovery_item.xml +++ b/app/src/main/res/layout/quick_feed_discovery_item.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:squareImageView="http://schemas.android.com/apk/de.danoeh.antennapod" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="4dp" @@ -12,7 +13,8 @@ android:layout_height="match_parent" android:elevation="4dp" android:outlineProvider="bounds" - android:foreground="?android:attr/selectableItemBackground"/> + android:foreground="?android:attr/selectableItemBackground" + squareImageView:direction="width" /> </LinearLayout> diff --git a/app/src/main/res/layout/search_fragment.xml b/app/src/main/res/layout/search_fragment.xml index e9e59b592..ff40af775 100644 --- a/app/src/main/res/layout/search_fragment.xml +++ b/app/src/main/res/layout/search_fragment.xml @@ -22,9 +22,21 @@ android:layout_height="wrap_content" android:layout_gravity="center"/> - <ListView + <androidx.recyclerview.widget.RecyclerView android:layout_below="@id/toolbar" - android:id="@+id/listview" + android:id="@+id/recyclerViewFeeds" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="12dp" + android:paddingRight="12dp" + android:clipToPadding="false"/> + + <androidx.recyclerview.widget.RecyclerView + android:layout_below="@id/recyclerViewFeeds" + android:id="@+id/recyclerView" + android:layout_marginTop="-4dp" + android:paddingTop="12dp" + android:clipToPadding="false" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout> diff --git a/app/src/main/res/layout/searchlist_item_feed.xml b/app/src/main/res/layout/searchlist_item_feed.xml new file mode 100644 index 000000000..f5e76801e --- /dev/null +++ b/app/src/main/res/layout/searchlist_item_feed.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:squareImageView="http://schemas.android.com/apk/de.danoeh.antennapod" + android:layout_width="match_parent" + android:layout_height="96dp" + android:padding="4dp" + android:clipToPadding="false"> + + <de.danoeh.antennapod.view.SquareImageView + android:id="@+id/discovery_cover" + android:layout_width="match_parent" + android:layout_height="96dp" + android:elevation="4dp" + android:outlineProvider="bounds" + android:foreground="?android:attr/selectableItemBackground" + squareImageView:direction="height" /> + +</LinearLayout> + diff --git a/app/src/main/res/layout/simple_list_fragment.xml b/app/src/main/res/layout/simple_list_fragment.xml index 91392d0a4..5bd2925ad 100644 --- a/app/src/main/res/layout/simple_list_fragment.xml +++ b/app/src/main/res/layout/simple_list_fragment.xml @@ -11,11 +11,12 @@ android:layout_alignParentTop="true" android:id="@+id/toolbar"/> - <ListView android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_below="@id/toolbar" - android:id="@android:id/list" - android:clipToPadding="false"/> + <androidx.recyclerview.widget.RecyclerView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_below="@id/toolbar" + android:id="@+id/recyclerView" + android:clipToPadding="false"/> <ProgressBar android:id="@+id/progLoading" diff --git a/app/src/main/res/layout/subscription_item.xml b/app/src/main/res/layout/subscription_item.xml index d065c5cf1..177608a54 100644 --- a/app/src/main/res/layout/subscription_item.xml +++ b/app/src/main/res/layout/subscription_item.xml @@ -1,7 +1,9 @@ <?xml version='1.0' encoding='utf-8'?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + xmlns:squareImageView="http://schemas.android.com/apk/de.danoeh.antennapod" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -10,7 +12,8 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="centerCrop" - tools:src="@mipmap/ic_launcher_round" /> + tools:src="@mipmap/ic_launcher_round" + squareImageView:direction="width"/> <com.joanzapata.iconify.widget.IconTextView android:id="@+id/txtvTitle" diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 0ec325e85..b92dd217d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -54,7 +54,6 @@ import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent; import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedComponent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; @@ -1649,18 +1648,11 @@ public class PlaybackService extends MediaBrowserServiceCompat { public void onPlayFromSearch(String query, Bundle extras) { Log.d(TAG, "onPlayFromSearch query=" + query + " extras=" + extras.toString()); - List<FeedComponent> results = FeedSearcher.performSearch(getBaseContext(), query, 0); - for (FeedComponent result : results) { - if (result instanceof FeedItem) { - try { - FeedMedia media = ((FeedItem) result).getMedia(); - mediaPlayer.playMediaObject(media, !media.localFileAvailable(), true, true); - return; - } catch (Exception e) { - Log.d(TAG, e.getMessage()); - e.printStackTrace(); - } - } + List<FeedItem> results = FeedSearcher.searchFeedItems(getBaseContext(), query, 0); + if (results.size() > 0 && results.get(0).getMedia() != null) { + FeedMedia media = results.get(0).getMedia(); + mediaPlayer.playMediaObject(media, !media.localFileAvailable(), true, true); + return; } onPlay(); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java index bbd7bbcf1..a8cc6cbee 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java @@ -126,8 +126,6 @@ public class DBWriter { } } EventBus.getDefault().post(FeedItemEvent.deletedMedia(Collections.singletonList(media.getItem()))); - EventBus.getDefault().post(new UnreadItemsUpdateEvent()); - return true; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java index 77c8d3b7f..9d75231d0 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java @@ -7,6 +7,7 @@ import de.danoeh.antennapod.core.feed.FeedComponent; import de.danoeh.antennapod.core.feed.FeedItem; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; @@ -19,32 +20,25 @@ public class FeedSearcher { } - /** - * Search through a feed, or all feeds, for episodes that match the query in either the title, - * chapter, or show notes. The search is first performed on titles, then chapters, and finally - * show notes. The list of resulting episodes also describes where the first match occurred - * (title, chapters, or show notes). - * - * @param context Used for database access - * @param query search query - * @param selectedFeed feed to search, 0 to search through all feeds - * @return list of episodes containing the query - */ @NonNull - public static List<FeedComponent> performSearch(final Context context, final String query, final long selectedFeed) { - final List<FeedComponent> result = new ArrayList<>(); + public static List<FeedItem> searchFeedItems(final Context context, final String query, final long selectedFeed) { try { FutureTask<List<FeedItem>> itemSearchTask = DBTasks.searchFeedItems(context, selectedFeed, query); itemSearchTask.run(); - if (selectedFeed == 0) { - FutureTask<List<Feed>> feedSearchTask = DBTasks.searchFeeds(context, query); - feedSearchTask.run(); - result.addAll(feedSearchTask.get()); - } - result.addAll(itemSearchTask.get()); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); + return itemSearchTask.get(); + } catch (ExecutionException | InterruptedException e) { + return Collections.emptyList(); + } + } + + @NonNull + public static List<Feed> searchFeeds(final Context context, final String query) { + try { + FutureTask<List<Feed>> feedSearchTask = DBTasks.searchFeeds(context, query); + feedSearchTask.run(); + return feedSearchTask.get(); + } catch (ExecutionException | InterruptedException e) { + return Collections.emptyList(); } - return result; } } diff --git a/core/src/main/res/layout/more_content_list_footer.xml b/core/src/main/res/layout/more_content_list_footer.xml index bfe9e89b6..f6d6a313c 100644 --- a/core/src/main/res/layout/more_content_list_footer.xml +++ b/core/src/main/res/layout/more_content_list_footer.xml @@ -1,28 +1,36 @@ <?xml version="1.0" encoding="utf-8"?> -<FrameLayout +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/main" + android:id="@+id/more_content_list_footer" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?attr/selectableItemBackground"> + android:layout_height="wrap_content" + android:foreground="?attr/selectableItemBackground" + android:background="?android:attr/windowBackground" + android:gravity="center" + android:padding="8dp"> <ImageView android:id="@+id/imgExpand" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="16dp" + android:layout_height="16dp" android:layout_gravity="center" - android:layout_margin="16dp" android:contentDescription="@string/load_next_page_label" app:srcCompat="?attr/ic_load_more" /> <ProgressBar android:id="@+id/progBar" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_margin="16dp" + android:layout_width="16dp" + android:layout_height="16dp" android:indeterminateOnly="true" android:visibility="gone" /> -</FrameLayout>
\ No newline at end of file + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/load_next_page_label" + android:textColor="?android:attr/textColorPrimary" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp"/> + +</LinearLayout>
\ No newline at end of file diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml index 44f112614..471e31439 100644 --- a/core/src/main/res/values/attrs.xml +++ b/core/src/main/res/values/attrs.xml @@ -57,6 +57,10 @@ <attr name="action_icon_color" format="color"/> <declare-styleable name="SquareImageView"> - <attr name="useMinimum" format="boolean" /> + <attr name="direction" format="enum"> + <enum name="width" value="0"/> + <enum name="height" value="1"/> + <enum name="minimum" value="2"/> + </attr> </declare-styleable> </resources> |