mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Use RecyclerView for catalogue
This commit is contained in:
		| @@ -77,32 +77,31 @@ public class Manga implements Serializable { | ||||
|         this.url = UrlUtil.getPath(url); | ||||
|     } | ||||
|  | ||||
|     public static void copyFromNetwork(Manga local, Manga network) { | ||||
|         if (network.title != null) | ||||
|             local.title = network.title; | ||||
|     public void copyFrom(Manga other) { | ||||
|         if (other.title != null) | ||||
|             title = other.title; | ||||
|  | ||||
|         if (network.author != null) | ||||
|             local.author = network.author; | ||||
|         if (other.author != null) | ||||
|             author = other.author; | ||||
|  | ||||
|         if (network.artist != null) | ||||
|             local.artist = network.artist; | ||||
|         if (other.artist != null) | ||||
|             artist = other.artist; | ||||
|  | ||||
|         if (network.url != null) | ||||
|             local.url = network.url; | ||||
|         if (other.url != null) | ||||
|             url = other.url; | ||||
|  | ||||
|         if (network.description != null) | ||||
|             local.description = network.description; | ||||
|         if (other.description != null) | ||||
|             description = other.description; | ||||
|  | ||||
|         if (network.genre != null) | ||||
|             local.genre = network.genre; | ||||
|         if (other.genre != null) | ||||
|             genre = other.genre; | ||||
|  | ||||
|         if (network.thumbnail_url != null) | ||||
|             local.thumbnail_url = network.thumbnail_url; | ||||
|         if (other.thumbnail_url != null) | ||||
|             thumbnail_url = other.thumbnail_url; | ||||
|  | ||||
|         local.status = network.status; | ||||
|  | ||||
|         local.initialized = true; | ||||
|         status = other.status; | ||||
|  | ||||
|         initialized = true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -3,71 +3,58 @@ package eu.kanade.mangafeed.ui.catalogue; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.ArrayAdapter; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import butterknife.Bind; | ||||
| import butterknife.ButterKnife; | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter; | ||||
| import eu.kanade.mangafeed.R; | ||||
| import eu.kanade.mangafeed.data.database.models.Manga; | ||||
|  | ||||
| public class CatalogueAdapter extends ArrayAdapter<Manga> { | ||||
| public class CatalogueAdapter extends FlexibleAdapter<CatalogueHolder, Manga> { | ||||
|  | ||||
|     private CatalogueFragment fragment; | ||||
|     private LayoutInflater inflater; | ||||
|  | ||||
|     public CatalogueAdapter(CatalogueFragment fragment) { | ||||
|         super(fragment.getActivity(), 0, new ArrayList<>()); | ||||
|         this.fragment = fragment; | ||||
|         inflater = fragment.getActivity().getLayoutInflater(); | ||||
|         mItems = new ArrayList<>(); | ||||
|         setHasStableIds(true); | ||||
|     } | ||||
|  | ||||
|     public void addItems(List<Manga> list) { | ||||
|         mItems.addAll(list); | ||||
|         notifyDataSetChanged(); | ||||
|     } | ||||
|  | ||||
|     public void clear() { | ||||
|         mItems.clear(); | ||||
|         notifyDataSetChanged(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public View getView(int position, View view, ViewGroup parent) { | ||||
|         Manga manga = getItem(position); | ||||
|  | ||||
|         ViewHolder holder; | ||||
|         if (view != null) { | ||||
|             holder = (ViewHolder) view.getTag(); | ||||
|         } else { | ||||
|             view = inflater.inflate(R.layout.item_catalogue, parent, false); | ||||
|             holder = new ViewHolder(view, fragment); | ||||
|             view.setTag(holder); | ||||
|         } | ||||
|         holder.onSetValues(manga); | ||||
|         return view; | ||||
|     public long getItemId(int position) { | ||||
|         return mItems.get(position).id; | ||||
|     } | ||||
|  | ||||
|     static class ViewHolder { | ||||
|         @Bind(R.id.title) TextView title; | ||||
|         @Bind(R.id.thumbnail) ImageView thumbnail; | ||||
|         @Bind(R.id.favorite_sticker) ImageView favorite_sticker; | ||||
|     @Override | ||||
|     public void updateDataSet(String param) { | ||||
|  | ||||
|         CataloguePresenter presenter; | ||||
|  | ||||
|         public ViewHolder(View view, CatalogueFragment fragment) { | ||||
|             ButterKnife.bind(this, view); | ||||
|             presenter = fragment.getPresenter(); | ||||
|         } | ||||
|  | ||||
|         public void onSetValues(Manga manga) { | ||||
|             title.setText(manga.title); | ||||
|  | ||||
|             if (manga.thumbnail_url != null) { | ||||
|                 presenter.coverCache.loadFromCacheOrNetwork(thumbnail, manga.thumbnail_url, | ||||
|                         presenter.getSource().getGlideHeaders()); | ||||
|             } else { | ||||
|                 thumbnail.setImageResource(android.R.color.transparent); | ||||
|             } | ||||
|  | ||||
|             if (manga.favorite) { | ||||
|                 favorite_sticker.setVisibility(View.VISIBLE); | ||||
|             } else { | ||||
|                 favorite_sticker.setVisibility(View.INVISIBLE); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public CatalogueHolder onCreateViewHolder(ViewGroup parent, int viewType) { | ||||
|         LayoutInflater inflater = fragment.getActivity().getLayoutInflater(); | ||||
|         View v = inflater.inflate(R.layout.item_catalogue, parent, false); | ||||
|         return new CatalogueHolder(v, this, fragment); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindViewHolder(CatalogueHolder holder, int position) { | ||||
|         final Manga manga = getItem(position); | ||||
|         holder.onSetValues(manga, fragment.getPresenter()); | ||||
|  | ||||
|         //When user scrolls this bind the correct selection status | ||||
|         //holder.itemView.setActivated(isSelected(position)); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,8 @@ package eu.kanade.mangafeed.ui.catalogue; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v7.widget.GridLayoutManager; | ||||
| import android.support.v7.widget.SearchView; | ||||
| import android.support.v7.widget.Toolbar; | ||||
| import android.text.TextUtils; | ||||
| @@ -14,8 +16,6 @@ import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.AdapterView; | ||||
| import android.widget.GridView; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.ProgressBar; | ||||
| import android.widget.Spinner; | ||||
|  | ||||
| @@ -24,15 +24,16 @@ import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| import butterknife.Bind; | ||||
| import butterknife.ButterKnife; | ||||
| import butterknife.OnItemClick; | ||||
| import eu.kanade.mangafeed.R; | ||||
| import eu.kanade.mangafeed.data.database.models.Manga; | ||||
| import eu.kanade.mangafeed.data.source.base.Source; | ||||
| import eu.kanade.mangafeed.ui.base.adapter.FlexibleViewHolder; | ||||
| import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment; | ||||
| import eu.kanade.mangafeed.ui.main.MainActivity; | ||||
| import eu.kanade.mangafeed.ui.manga.MangaActivity; | ||||
| import eu.kanade.mangafeed.util.ToastUtil; | ||||
| import eu.kanade.mangafeed.widget.EndlessScrollListener; | ||||
| import eu.kanade.mangafeed.widget.AutofitRecyclerView; | ||||
| import eu.kanade.mangafeed.widget.EndlessRecyclerScrollListener; | ||||
| import icepick.State; | ||||
| import nucleus.factory.RequiresPresenter; | ||||
| import rx.Subscription; | ||||
| @@ -40,16 +41,16 @@ import rx.android.schedulers.AndroidSchedulers; | ||||
| import rx.subjects.PublishSubject; | ||||
|  | ||||
| @RequiresPresenter(CataloguePresenter.class) | ||||
| public class CatalogueFragment extends BaseRxFragment<CataloguePresenter> { | ||||
| public class CatalogueFragment extends BaseRxFragment<CataloguePresenter> implements FlexibleViewHolder.OnListItemClickListener { | ||||
|  | ||||
|     @Bind(R.id.gridView) GridView gridView; | ||||
|     @Bind(R.id.recycler) AutofitRecyclerView recycler; | ||||
|     @Bind(R.id.progress) ProgressBar progress; | ||||
|     @Bind(R.id.progress_grid) ProgressBar progressGrid; | ||||
|  | ||||
|     private Toolbar toolbar; | ||||
|     private Spinner spinner; | ||||
|     private CatalogueAdapter adapter; | ||||
|     private EndlessScrollListener scrollListener; | ||||
|     private EndlessRecyclerScrollListener scrollListener; | ||||
|  | ||||
|     @State String query = ""; | ||||
|     @State int selectedIndex = -1; | ||||
| @@ -75,10 +76,12 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter> { | ||||
|         ButterKnife.bind(this, view); | ||||
|  | ||||
|         // Initialize adapter and scroll listener | ||||
|         GridLayoutManager layoutManager = (GridLayoutManager) recycler.getLayoutManager(); | ||||
|         adapter = new CatalogueAdapter(this); | ||||
|         scrollListener = new EndlessScrollListener(this::requestNextPage); | ||||
|         gridView.setAdapter(adapter); | ||||
|         gridView.setOnScrollListener(scrollListener); | ||||
|         scrollListener = new EndlessRecyclerScrollListener(layoutManager, this::requestNextPage); | ||||
|         recycler.setHasFixedSize(true); | ||||
|         recycler.setAdapter(adapter); | ||||
|         recycler.addOnScrollListener(scrollListener); | ||||
|  | ||||
|         // Create toolbar spinner | ||||
|         Context themedContext = getBaseActivity().getSupportActionBar() != null ? | ||||
| @@ -192,9 +195,7 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter> { | ||||
|  | ||||
|         query = newQuery; | ||||
|         showProgressBar(); | ||||
|         // Set adapter again for scrolling to top: http://stackoverflow.com/a/17577981/3263582 | ||||
|         gridView.setAdapter(adapter); | ||||
|         gridView.setSelection(0); | ||||
|         recycler.getLayoutManager().scrollToPosition(0); | ||||
|  | ||||
|         getPresenter().restartRequest(query); | ||||
|     } | ||||
| @@ -212,48 +213,23 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter> { | ||||
|             adapter.clear(); | ||||
|             scrollListener.resetScroll(); | ||||
|         } | ||||
|         adapter.addAll(pair.second); | ||||
|         adapter.addItems(pair.second); | ||||
|     } | ||||
|  | ||||
|     public void onAddPageError() { | ||||
|         hideProgressBar(); | ||||
|     } | ||||
|  | ||||
|     @OnItemClick(R.id.gridView) | ||||
|     public void onMangaClick(int position) { | ||||
|         Manga selectedManga = adapter.getItem(position); | ||||
|  | ||||
|         Intent intent = MangaActivity.newIntent(getActivity(), selectedManga); | ||||
|         intent.putExtra(MangaActivity.MANGA_ONLINE, true); | ||||
|         startActivity(intent); | ||||
|     } | ||||
|  | ||||
|     public void updateImage(Manga manga) { | ||||
|         ImageView imageView = getImageView(getMangaIndex(manga)); | ||||
|         if (imageView != null && manga.thumbnail_url != null) { | ||||
|             getPresenter().coverCache.loadFromNetwork(imageView, manga.thumbnail_url, | ||||
|                     getPresenter().getSource().getGlideHeaders()); | ||||
|         CatalogueHolder holder = getHolder(manga); | ||||
|         if (holder != null) { | ||||
|             holder.setImage(manga, getPresenter()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private ImageView getImageView(int position) { | ||||
|         if (position == -1) return null; | ||||
|  | ||||
|         View v = gridView.getChildAt(position - | ||||
|                 gridView.getFirstVisiblePosition()); | ||||
|  | ||||
|         if (v == null) return null; | ||||
|  | ||||
|         return (ImageView) v.findViewById(R.id.thumbnail); | ||||
|     } | ||||
|  | ||||
|     private int getMangaIndex(Manga manga) { | ||||
|         for (int i = adapter.getCount() - 1; i >= 0; i--) { | ||||
|             if (manga.id.equals(adapter.getItem(i).id)) { | ||||
|                 return i; | ||||
|             } | ||||
|         } | ||||
|         return -1; | ||||
|     @Nullable | ||||
|     private CatalogueHolder getHolder(Manga manga) { | ||||
|         return (CatalogueHolder) recycler.findViewHolderForItemId(manga.id); | ||||
|     } | ||||
|  | ||||
|     private void showProgressBar() { | ||||
| @@ -269,4 +245,18 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter> { | ||||
|         progressGrid.setVisibility(ProgressBar.GONE); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onListItemClick(int position) { | ||||
|         final Manga selectedManga = adapter.getItem(position); | ||||
|  | ||||
|         Intent intent = MangaActivity.newIntent(getActivity(), selectedManga); | ||||
|         intent.putExtra(MangaActivity.MANGA_ONLINE, true); | ||||
|         startActivity(intent); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onListItemLongClick(int position) { | ||||
|         // Do nothing | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,38 @@ | ||||
| package eu.kanade.mangafeed.ui.catalogue; | ||||
|  | ||||
| import android.view.View; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import butterknife.Bind; | ||||
| import butterknife.ButterKnife; | ||||
| import eu.kanade.mangafeed.R; | ||||
| import eu.kanade.mangafeed.data.database.models.Manga; | ||||
| import eu.kanade.mangafeed.ui.base.adapter.FlexibleViewHolder; | ||||
|  | ||||
| public class CatalogueHolder extends FlexibleViewHolder { | ||||
|  | ||||
|     @Bind(R.id.title) TextView title; | ||||
|     @Bind(R.id.thumbnail) ImageView thumbnail; | ||||
|     @Bind(R.id.favorite_sticker) ImageView favoriteSticker; | ||||
|  | ||||
|     public CatalogueHolder(View view, CatalogueAdapter adapter, OnListItemClickListener listener) { | ||||
|         super(view, adapter, listener); | ||||
|         ButterKnife.bind(this, view); | ||||
|     } | ||||
|  | ||||
|     public void onSetValues(Manga manga, CataloguePresenter presenter) { | ||||
|         title.setText(manga.title); | ||||
|         favoriteSticker.setVisibility(manga.favorite ? View.VISIBLE : View.GONE); | ||||
|         setImage(manga, presenter); | ||||
|     } | ||||
|  | ||||
|     public void setImage(Manga manga, CataloguePresenter presenter) { | ||||
|         if (manga.thumbnail_url != null) { | ||||
|             presenter.coverCache.loadFromNetwork(thumbnail, manga.thumbnail_url, | ||||
|                     presenter.getSource().getGlideHeaders()); | ||||
|         } else { | ||||
|             thumbnail.setImageResource(android.R.color.transparent); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -60,12 +60,12 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> { | ||||
|                 () -> pager.pages().concatMap( | ||||
|                         page -> getMangaObs(page + 1) | ||||
|                                 .map(mangas -> Pair.create(page, mangas)) | ||||
|                                 .doOnNext(pair -> { | ||||
|                                     if (mangaDetailSubject != null) | ||||
|                                         mangaDetailSubject.onNext(pair.second); | ||||
|                                 }) | ||||
|                                 .observeOn(AndroidSchedulers.mainThread())), | ||||
|                 (view, page) -> { | ||||
|                     view.onAddPage(page); | ||||
|                     if (mangaDetailSubject != null) | ||||
|                         mangaDetailSubject.onNext(page.second); | ||||
|                 }, | ||||
|                 CatalogueFragment::onAddPage, | ||||
|                 (view, error) -> { | ||||
|                     view.onAddPageError(); | ||||
|                     Timber.e(error.getMessage()); | ||||
| @@ -73,14 +73,14 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> { | ||||
|  | ||||
|         restartableLatestCache(GET_MANGA_DETAIL, | ||||
|                 () -> mangaDetailSubject | ||||
|                         .observeOn(Schedulers.io()) | ||||
|                         .flatMap(Observable::from) | ||||
|                         .filter(manga -> !manga.initialized) | ||||
|                         .window(3) | ||||
|                         .concatMap(pack -> pack.concatMap(this::getMangaDetails)) | ||||
|                         .filter(manga -> manga.initialized) | ||||
|                         .onBackpressureBuffer() | ||||
|                         .observeOn(AndroidSchedulers.mainThread()), | ||||
|                 (view, manga) -> view.updateImage(manga), | ||||
|                 CatalogueFragment::updateImage, | ||||
|                 (view, error) -> Timber.e(error.getMessage())); | ||||
|     } | ||||
|  | ||||
| @@ -147,7 +147,7 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> { | ||||
|         return source.pullMangaFromNetwork(manga.url) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .flatMap(networkManga -> { | ||||
|                     Manga.copyFromNetwork(manga, networkManga); | ||||
|                     manga.copyFrom(networkManga); | ||||
|                     db.insertManga(manga).executeAsBlocking(); | ||||
|                     return Observable.just(manga); | ||||
|                 }) | ||||
|   | ||||
| @@ -93,7 +93,7 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> { | ||||
|     private Observable<Manga> fetchMangaObs() { | ||||
|         return source.pullMangaFromNetwork(manga.url) | ||||
|                 .flatMap(networkManga -> { | ||||
|                     Manga.copyFromNetwork(manga, networkManga); | ||||
|                     manga.copyFrom(networkManga); | ||||
|                     db.insertManga(manga).executeAsBlocking(); | ||||
|                     return Observable.just(manga); | ||||
|                 }) | ||||
|   | ||||
| @@ -0,0 +1,49 @@ | ||||
| package eu.kanade.mangafeed.widget; | ||||
|  | ||||
| import android.support.v7.widget.GridLayoutManager; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
|  | ||||
| import rx.functions.Action0; | ||||
|  | ||||
| public class EndlessRecyclerScrollListener extends RecyclerView.OnScrollListener { | ||||
|  | ||||
|     private int previousTotal = 0; // The total number of items in the dataset after the last load | ||||
|     private boolean loading = true; // True if we are still waiting for the last set of data to load. | ||||
|     private int visibleThreshold = 5; // The minimum amount of items to have below your current scroll position before loading more. | ||||
|     int firstVisibleItem, visibleItemCount, totalItemCount; | ||||
|  | ||||
|     private GridLayoutManager layoutManager; | ||||
|  | ||||
|     private Action0 requestNext; | ||||
|  | ||||
|     public EndlessRecyclerScrollListener(GridLayoutManager layoutManager, Action0 requestNext) { | ||||
|         this.layoutManager = layoutManager; | ||||
|         this.requestNext = requestNext; | ||||
|     } | ||||
|  | ||||
|     public void resetScroll() { | ||||
|         previousTotal = 0; | ||||
|         loading = true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onScrolled(RecyclerView recyclerView, int dx, int dy) { | ||||
|         super.onScrolled(recyclerView, dx, dy); | ||||
|  | ||||
|         visibleItemCount = recyclerView.getChildCount(); | ||||
|         totalItemCount = layoutManager.getItemCount(); | ||||
|         firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); | ||||
|  | ||||
|         if (loading && (totalItemCount > previousTotal)) { | ||||
|             loading = false; | ||||
|             previousTotal = totalItemCount; | ||||
|         } | ||||
|         if (!loading && (totalItemCount - visibleItemCount) | ||||
|                 <= (firstVisibleItem + visibleThreshold)) { | ||||
|             // End has been reached | ||||
|             requestNext.call(); | ||||
|             loading = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user