Browse Source

Use RecyclerView for catalogue

inorichi 9 years ago
parent
commit
868058a50b

+ 17 - 18
app/src/main/java/eu/kanade/mangafeed/data/database/models/Manga.java

@@ -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

+ 32 - 45
app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueAdapter.java

@@ -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);
     }
 
-    @Override
-    public View getView(int position, View view, ViewGroup parent) {
-        Manga manga = getItem(position);
+    public void addItems(List<Manga> list) {
+        mItems.addAll(list);
+        notifyDataSetChanged();
+    }
 
-        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 void clear() {
+        mItems.clear();
+        notifyDataSetChanged();
     }
 
-    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 long getItemId(int position) {
+        return mItems.get(position).id;
+    }
 
-        CataloguePresenter presenter;
+    @Override
+    public void updateDataSet(String param) {
 
-        public ViewHolder(View view, CatalogueFragment fragment) {
-            ButterKnife.bind(this, view);
-            presenter = fragment.getPresenter();
-        }
+    }
 
-        public void onSetValues(Manga manga) {
-            title.setText(manga.title);
+    @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);
+    }
 
-            if (manga.thumbnail_url != null) {
-                presenter.coverCache.loadFromCacheOrNetwork(thumbnail, manga.thumbnail_url,
-                        presenter.getSource().getGlideHeaders());
-            } else {
-                thumbnail.setImageResource(android.R.color.transparent);
-            }
+    @Override
+    public void onBindViewHolder(CatalogueHolder holder, int position) {
+        final Manga manga = getItem(position);
+        holder.onSetValues(manga, fragment.getPresenter());
 
-            if (manga.favorite) {
-                favorite_sticker.setVisibility(View.VISIBLE);
-            } else {
-                favorite_sticker.setVisibility(View.INVISIBLE);
-            }
-        }
+        //When user scrolls this bind the correct selection status
+        //holder.itemView.setActivated(isSelected(position));
     }
+
 }

+ 35 - 45
app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java

@@ -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
+    }
 }

+ 38 - 0
app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueHolder.java

@@ -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);
+        }
+    }
+}

+ 8 - 8
app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CataloguePresenter.java

@@ -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);
                 })

+ 1 - 1
app/src/main/java/eu/kanade/mangafeed/ui/manga/info/MangaInfoPresenter.java

@@ -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);
                 })

+ 49 - 0
app/src/main/java/eu/kanade/mangafeed/widget/EndlessRecyclerScrollListener.java

@@ -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;
+        }
+    }
+
+}

+ 2 - 2
app/src/main/res/layout/fragment_catalogue.xml

@@ -15,8 +15,8 @@
         android:layout_gravity="center_vertical|center_horizontal"
         android:visibility="gone"/>
 
-    <GridView
-        android:id="@+id/gridView"
+    <eu.kanade.mangafeed.widget.AutofitRecyclerView
+        android:id="@+id/recycler"
         style="@style/AppTheme.GridView"
         android:layout_height="0dp"
         android:layout_weight="1"