diff options
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod/fragment')
7 files changed, 414 insertions, 375 deletions
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); } } |