From d859947c7ccb804cc40bd7576f7e9b3e16510334 Mon Sep 17 00:00:00 2001 From: inorichi Date: Sat, 5 Dec 2015 12:40:47 +0100 Subject: [PATCH] Remove view logic from catalogue presenter and improve catalogue fragment --- .../ui/catalogue/CatalogueFragment.java | 205 +++++++++++------- .../ui/catalogue/CataloguePresenter.java | 116 +++------- .../ui/manga/chapter/ChaptersPresenter.java | 4 +- 3 files changed, 160 insertions(+), 165 deletions(-) diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java index 295bbc4cc..99f179303 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java @@ -3,6 +3,7 @@ package eu.kanade.mangafeed.ui.catalogue; import android.content.Intent; import android.os.Bundle; import android.support.v7.widget.SearchView; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -14,6 +15,7 @@ import android.widget.ImageView; import android.widget.ProgressBar; import java.util.List; +import java.util.concurrent.TimeUnit; import butterknife.Bind; import butterknife.ButterKnife; @@ -24,7 +26,13 @@ import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment; import eu.kanade.mangafeed.ui.manga.MangaActivity; import eu.kanade.mangafeed.util.PageBundle; import eu.kanade.mangafeed.widget.EndlessScrollListener; +import icepick.Icepick; +import icepick.State; import nucleus.factory.RequiresPresenter; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; @RequiresPresenter(CataloguePresenter.class) public class CatalogueFragment extends BaseRxFragment { @@ -35,7 +43,12 @@ public class CatalogueFragment extends BaseRxFragment { private CatalogueAdapter adapter; private EndlessScrollListener scrollListener; - private String search; + + @State String query = ""; + private final int SEARCH_TIMEOUT = 1000; + + private PublishSubject queryDebouncerSubject; + private Subscription queryDebouncerSubscription; public final static String SOURCE_ID = "source_id"; @@ -48,63 +61,132 @@ public class CatalogueFragment extends BaseRxFragment { } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public void onCreate(Bundle savedState) { + super.onCreate(savedState); + Icepick.restoreInstanceState(this, savedState); setHasOptionsMenu(true); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_catalogue, container, false); ButterKnife.bind(this, view); - initializeAdapter(); - initializeScrollListener(); + // Initialize adapter and scroll listener + adapter = new CatalogueAdapter(this); + scrollListener = new EndlessScrollListener(this::requestNextPage); + gridView.setAdapter(adapter); + gridView.setOnScrollListener(scrollListener); - int source_id = getArguments().getInt(SOURCE_ID, -1); + int sourceId = getArguments().getInt(SOURCE_ID, -1); showProgressBar(); + if (savedState == null) + getPresenter().startRequesting(sourceId); - if (savedInstanceState == null) - getPresenter().startRequesting(source_id); - + setToolbarTitle(getPresenter().getSource().getName()); return view; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.catalogue_list, menu); - initializeSearch(menu); - } - private void initializeSearch(Menu menu) { + // Initialize search menu MenuItem searchItem = menu.findItem(R.id.action_search); - final SearchView sv = (SearchView) searchItem.getActionView(); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + final SearchView searchView = (SearchView) searchItem.getActionView(); + + if (!TextUtils.isEmpty(query)) { + searchItem.expandActionView(); + searchView.setQuery(query, true); + searchView.clearFocus(); + } + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - getPresenter().onSearchEvent(query, true); + onSearchEvent(query, true); return true; } @Override public boolean onQueryTextChange(String newText) { - getPresenter().onSearchEvent(newText, false); + onSearchEvent(newText, false); return true; } }); - if (search != null && !search.equals("")) { - searchItem.expandActionView(); - sv.setQuery(search, true); - sv.clearFocus(); + } + + @Override + public void onStart() { + super.onStart(); + initializeSearchSubscription(); + } + + @Override + public void onStop() { + destroySearchSubscription(); + super.onStop(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + Icepick.saveInstanceState(this, outState); + super.onSaveInstanceState(outState); + } + + private void initializeSearchSubscription() { + queryDebouncerSubject = PublishSubject.create(); + queryDebouncerSubscription = queryDebouncerSubject + .debounce(SEARCH_TIMEOUT, TimeUnit.MILLISECONDS) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::restartRequest); + } + + private void destroySearchSubscription() { + queryDebouncerSubscription.unsubscribe(); + } + + private void onSearchEvent(String query, boolean now) { + // If the query is not debounced, resolve it instantly + if (now) + restartRequest(query); + else if (queryDebouncerSubject != null) + queryDebouncerSubject.onNext(query); + } + + private void restartRequest(String newQuery) { + // If text didn't change, do nothing + if (query.equals(newQuery)) return; + + query = newQuery; + showProgressBar(); + // Set adapter again for scrolling to top: http://stackoverflow.com/a/17577981/3263582 + gridView.setAdapter(adapter); + gridView.setSelection(0); + + getPresenter().restartRequest(query); + } + + private void requestNextPage() { + if (getPresenter().hasNextPage()) { + showGridProgressBar(); + getPresenter().requestNext(); } } - public void initializeAdapter() { - adapter = new CatalogueAdapter(this); - gridView.setAdapter(adapter); + public void onAddPage(PageBundle> page) { + hideProgressBar(); + if (page.page == 0) { + adapter.clear(); + scrollListener.resetScroll(); + } + adapter.addAll(page.data); + } + + public void onAddPageError() { + hideProgressBar(); } @OnItemClick(R.id.gridView) @@ -116,37 +198,23 @@ public class CatalogueFragment extends BaseRxFragment { startActivity(intent); } - public void initializeScrollListener() { - scrollListener = new EndlessScrollListener(this::requestNext); - gridView.setOnScrollListener(scrollListener); - } - - public void requestNext() { - if (getPresenter().requestNext()) - showGridProgressBar(); - } - - public void showProgressBar() { - progress.setVisibility(ProgressBar.VISIBLE); - } - - public void showGridProgressBar() { - progressGrid.setVisibility(ProgressBar.VISIBLE); - } - - public void hideProgressBar() { - progress.setVisibility(ProgressBar.GONE); - progressGrid.setVisibility(ProgressBar.GONE); - } - - public void onAddPage(PageBundle> page) { - hideProgressBar(); - if (page.page == 0) { - gridView.setSelection(0); - adapter.clear(); - scrollListener.resetScroll(); + 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()); } - adapter.addAll(page.data); + } + + 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) { @@ -158,28 +226,17 @@ public class CatalogueFragment extends BaseRxFragment { return -1; } - 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 void showProgressBar() { + progress.setVisibility(ProgressBar.VISIBLE); } - 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()); - } + private void showGridProgressBar() { + progressGrid.setVisibility(ProgressBar.VISIBLE); } - public void restoreSearch(String mSearchName) { - search = mSearchName; + private void hideProgressBar() { + progress.setVisibility(ProgressBar.GONE); + progressGrid.setVisibility(ProgressBar.GONE); } + } diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CataloguePresenter.java b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CataloguePresenter.java index d5e9e4586..8d3cca58b 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CataloguePresenter.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CataloguePresenter.java @@ -1,11 +1,11 @@ package eu.kanade.mangafeed.ui.catalogue; import android.os.Bundle; +import android.text.TextUtils; import com.pushtorefresh.storio.sqlite.operations.put.PutResult; import java.util.List; -import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -18,9 +18,7 @@ import eu.kanade.mangafeed.data.source.model.MangasPage; import eu.kanade.mangafeed.ui.base.presenter.BasePresenter; import eu.kanade.mangafeed.util.PageBundle; import eu.kanade.mangafeed.util.RxPager; -import icepick.State; import rx.Observable; -import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; @@ -32,18 +30,14 @@ public class CataloguePresenter extends BasePresenter { @Inject DatabaseHelper db; @Inject CoverCache coverCache; - private Source selectedSource; + private Source source; - @State protected String searchName; - @State protected boolean searchMode; - private final int SEARCH_TIMEOUT = 1000; + private String query; private int currentPage; private RxPager pager; private MangasPage lastMangasPage; - private Subscription queryDebouncerSubscription; - private PublishSubject queryDebouncerSubject; private PublishSubject> mangaDetailSubject; private static final int GET_MANGA_LIST = 1; @@ -65,7 +59,10 @@ public class CataloguePresenter extends BasePresenter { if (mangaDetailSubject != null) mangaDetailSubject.onNext(page.data); }, - (view, error) -> Timber.e(error.fillInStackTrace(), error.getMessage())); + (view, error) -> { + view.onAddPageError(); + Timber.e(error.getMessage()); + }); restartableLatestCache(GET_MANGA_DETAIL, () -> mangaDetailSubject @@ -77,46 +74,28 @@ public class CataloguePresenter extends BasePresenter { .filter(manga -> manga.initialized) .onBackpressureBuffer() .observeOn(AndroidSchedulers.mainThread()), - (view, manga) -> { - view.updateImage(manga); - }, - (view, error) -> Timber.e(error.fillInStackTrace(), error.getMessage())); - - initializeSearch(); - } - - @Override - protected void onTakeView(CatalogueFragment view) { - super.onTakeView(view); - - view.setToolbarTitle(selectedSource.getName()); - - if (searchMode) - view.restoreSearch(searchName); + (view, manga) -> view.updateImage(manga), + (view, error) -> Timber.e(error.getMessage())); } public void startRequesting(int sourceId) { - selectedSource = sourceManager.get(sourceId); - restartRequest(); + source = sourceManager.get(sourceId); + restartRequest(null); } - private void restartRequest() { + public void restartRequest(String query) { + this.query = query; stop(GET_MANGA_LIST); currentPage = 1; pager = new RxPager(); - if (getView() != null) - getView().showProgressBar(); start(GET_MANGA_DETAIL); start(GET_MANGA_LIST); } - public boolean requestNext() { - if (lastMangasPage.nextPageUrl == null) - return false; - - pager.requestNext(++currentPage); - return true; + public void requestNext() { + if (hasNextPage()) + pager.requestNext(++currentPage); } private Observable> getMangaObs(int page) { @@ -125,11 +104,9 @@ public class CataloguePresenter extends BasePresenter { nextMangasPage.url = lastMangasPage.nextPageUrl; } - Observable obs; - if (searchMode) - obs = selectedSource.searchMangasFromNetwork(nextMangasPage, searchName); - else - obs = selectedSource.pullPopularMangasFromNetwork(nextMangasPage); + Observable obs = !TextUtils.isEmpty(query) ? + source.searchMangasFromNetwork(nextMangasPage, query) : + source.pullPopularMangasFromNetwork(nextMangasPage); return obs.subscribeOn(Schedulers.io()) .doOnNext(mangasPage -> lastMangasPage = mangasPage) @@ -139,7 +116,7 @@ public class CataloguePresenter extends BasePresenter { } private Manga networkToLocalManga(Manga networkManga) { - List dbResult = db.getManga(networkManga.url, selectedSource.getSourceId()).executeAsBlocking(); + List dbResult = db.getManga(networkManga.url, source.getSourceId()).executeAsBlocking(); Manga localManga = !dbResult.isEmpty() ? dbResult.get(0) : null; if (localManga == null) { PutResult result = db.insertManga(networkManga).executeAsBlocking(); @@ -149,62 +126,23 @@ public class CataloguePresenter extends BasePresenter { return localManga; } - private void initializeSearch() { - if (queryDebouncerSubscription != null) - return; - - searchName = ""; - searchMode = false; - queryDebouncerSubject = PublishSubject.create(); - - add(queryDebouncerSubscription = queryDebouncerSubject - .debounce(SEARCH_TIMEOUT, TimeUnit.MILLISECONDS) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::queryFromSearch)); - } - private Observable getMangaDetails(final Manga manga) { - return selectedSource.pullMangaFromNetwork(manga.url) + return source.pullMangaFromNetwork(manga.url) .subscribeOn(Schedulers.io()) .flatMap(networkManga -> { Manga.copyFromNetwork(manga, networkManga); db.insertManga(manga).executeAsBlocking(); return Observable.just(manga); }) - .onErrorResumeNext(error -> { - return Observable.just(manga); - }); - } - - public void onSearchEvent(String query, boolean now) { - // If the query is empty or not debounced, resolve it instantly - if (now || query.equals("")) - queryFromSearch(query); - else if (queryDebouncerSubject != null) - queryDebouncerSubject.onNext(query); - } - - private void queryFromSearch(String query) { - // If text didn't change, do nothing - if (searchName.equals(query)) { - return; - } - // If going to search mode - else if (searchName.equals("") && !query.equals("")) { - searchMode = true; - } - // If going to normal mode - else if (!searchName.equals("") && query.equals("")) { - searchMode = false; - } - - searchName = query; - restartRequest(); + .onErrorResumeNext(error -> Observable.just(manga)); } public Source getSource() { - return selectedSource; + return source; + } + + public boolean hasNextPage() { + return lastMangasPage != null && lastMangasPage.nextPageUrl != null; } } diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/manga/chapter/ChaptersPresenter.java b/app/src/main/java/eu/kanade/mangafeed/ui/manga/chapter/ChaptersPresenter.java index 60c1151cc..553020ff8 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/manga/chapter/ChaptersPresenter.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/manga/chapter/ChaptersPresenter.java @@ -133,8 +133,8 @@ public class ChaptersPresenter extends BasePresenter { observable = observable.filter(chapter -> chapter.status == Download.DOWNLOADED); } return observable.toSortedList((chapter, chapter2) -> sortOrderAToZ ? - Float.compare(chapter.chapter_number, chapter2.chapter_number) : - Float.compare(chapter2.chapter_number, chapter.chapter_number)); + Float.compare(chapter2.chapter_number, chapter.chapter_number) : + Float.compare(chapter.chapter_number, chapter2.chapter_number)); } private void setChapterStatus(Chapter chapter) {