diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java index 3a8a75413..7ffddb21f 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java @@ -102,6 +102,11 @@ public class DatabaseHelper implements MangaManager, ChapterManager { return mMangaManager.getManga(id); } + @Override + public Manga getMangaBlock(String url) { + return mMangaManager.getMangaBlock(url); + } + @Override public Observable insertManga(Manga manga) { return mMangaManager.insertManga(manga); @@ -112,6 +117,11 @@ public class DatabaseHelper implements MangaManager, ChapterManager { return mMangaManager.insertMangas(mangas); } + @Override + public PutResult insertMangaBlock(Manga manga) { + return mMangaManager.insertMangaBlock(manga); + } + @Override public Observable deleteManga(Manga manga) { return mMangaManager.deleteManga(manga); diff --git a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java index b65585905..f1c5d4871 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java @@ -20,10 +20,14 @@ public interface MangaManager { Observable> getManga(int id); + Manga getMangaBlock(String url); + Observable insertManga(Manga manga); Observable> insertMangas(List mangas); + PutResult insertMangaBlock(Manga manga); + Observable deleteManga(Manga manga); Observable> deleteMangas(List mangas); diff --git a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java index 79046fb11..4d6d1a0a2 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java @@ -70,6 +70,24 @@ public class MangaManagerImpl extends BaseManager implements MangaManager { return null; } + @Override + public Manga getMangaBlock(String url) { + List result = db.get() + .listOfObjects(Manga.class) + .withQuery(Query.builder() + .table(MangasTable.TABLE) + .where(MangasTable.COLUMN_URL + "=?") + .whereArgs(url) + .build()) + .prepare() + .executeAsBlocking(); + + if (result.isEmpty()) + return null; + + return result.get(0); + } + public Observable insertManga(Manga manga) { return db.put() .object(manga) @@ -84,6 +102,13 @@ public class MangaManagerImpl extends BaseManager implements MangaManager { .createObservable(); } + public PutResult insertMangaBlock(Manga manga) { + return db.put() + .object(manga) + .prepare() + .executeAsBlocking(); + } + public Observable deleteManga(Manga manga) { return db.delete() .object(manga) diff --git a/app/src/main/java/eu/kanade/mangafeed/data/tables/MangasTable.java b/app/src/main/java/eu/kanade/mangafeed/data/tables/MangasTable.java index f5ceeb685..d51d109a0 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/tables/MangasTable.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/tables/MangasTable.java @@ -83,6 +83,8 @@ public class MangasTable { + COLUMN_INITIALIZED + " BOOLEAN NOT NULL, " + COLUMN_VIEWER + " INTEGER NOT NULL, " + COLUMN_CHAPTER_ORDER + " INTEGER NOT NULL" - + ");"; + + ");" + + "CREATE INDEX " + TABLE + "_" + COLUMN_URL + "_index ON " + TABLE + "(" + COLUMN_URL + ");"; + } } diff --git a/app/src/main/java/eu/kanade/mangafeed/presenter/CatalogueListPresenter.java b/app/src/main/java/eu/kanade/mangafeed/presenter/CatalogueListPresenter.java index 48f6ba33c..9f6ac58d3 100644 --- a/app/src/main/java/eu/kanade/mangafeed/presenter/CatalogueListPresenter.java +++ b/app/src/main/java/eu/kanade/mangafeed/presenter/CatalogueListPresenter.java @@ -12,11 +12,12 @@ import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.ui.adapter.CatalogueListHolder; import eu.kanade.mangafeed.view.CatalogueListView; import rx.Observable; +import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import uk.co.ribot.easyadapter.EasyAdapter; -public class CatalogueListPresenter { +public class CatalogueListPresenter extends BasePresenter { CatalogueListView view; EasyAdapter adapter; @@ -25,6 +26,8 @@ public class CatalogueListPresenter { @Inject SourceManager sourceManager; @Inject DatabaseHelper db; + private Subscription mMangaFetchSubscription; + public CatalogueListPresenter(CatalogueListView view) { this.view = view; @@ -38,24 +41,32 @@ public class CatalogueListPresenter { adapter = new EasyAdapter<>(view.getActivity(), CatalogueListHolder.class); view.setAdapter(adapter); + view.setScrollListener(); - getMangasFromSource(); + getMangasFromSource(1); } - private void getMangasFromSource() { - selectedSource.pullPopularMangasFromNetwork(1) + public void getMangasFromSource(int page) { + subscriptions.remove(mMangaFetchSubscription); + + mMangaFetchSubscription = selectedSource.pullPopularMangasFromNetwork(page) .subscribeOn(Schedulers.io()) - .flatMap(Observable::from) - .flatMap(networkManga -> db.getManga(networkManga.url) - .flatMap(result -> { - if (result.size() == 0) { - return db.insertManga(networkManga) - .flatMap(i -> Observable.just(networkManga)); - } - return Observable.just(networkManga); - })) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(adapter::addItem); + .flatMap(Observable::from) + .map(this::networkToLocalManga) + .toList() + .subscribe(adapter::addItems); + + subscriptions.add(mMangaFetchSubscription); + } + + private Manga networkToLocalManga(Manga networkManga) { + Manga localManga = db.getMangaBlock(networkManga.url); + if (localManga == null) { + db.insertMangaBlock(networkManga); + localManga = db.getMangaBlock(networkManga.url); + } + return localManga; } } diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/activity/CatalogueListActivity.java b/app/src/main/java/eu/kanade/mangafeed/ui/activity/CatalogueListActivity.java index 7c681ec72..a938dbaaa 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/activity/CatalogueListActivity.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/activity/CatalogueListActivity.java @@ -10,6 +10,7 @@ import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.presenter.CatalogueListPresenter; import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.view.CatalogueListView; +import eu.kanade.mangafeed.widget.EndlessScrollListener; import uk.co.ribot.easyadapter.EasyAdapter; public class CatalogueListActivity extends BaseActivity implements CatalogueListView { @@ -24,7 +25,7 @@ public class CatalogueListActivity extends BaseActivity implements CatalogueList private Source source; @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_catalogue_list); ButterKnife.bind(this); @@ -35,6 +36,12 @@ public class CatalogueListActivity extends BaseActivity implements CatalogueList presenter.initializeSource(); } + @Override + public void onDestroy() { + super.onDestroy(); + presenter.destroySubscriptions(); + } + public void setSource(Source source) { this.source = source; setToolbarTitle(source.getName()); @@ -44,4 +51,14 @@ public class CatalogueListActivity extends BaseActivity implements CatalogueList manga_list.setAdapter(adapter); } + public void setScrollListener() { + manga_list.setOnScrollListener(new EndlessScrollListener() { + @Override + public boolean onLoadMore(int page, int totalItemsCount) { + presenter.getMangasFromSource(page); + return true; + } + }); + } + } diff --git a/app/src/main/java/eu/kanade/mangafeed/view/CatalogueListView.java b/app/src/main/java/eu/kanade/mangafeed/view/CatalogueListView.java index 8cf2f7355..914598535 100644 --- a/app/src/main/java/eu/kanade/mangafeed/view/CatalogueListView.java +++ b/app/src/main/java/eu/kanade/mangafeed/view/CatalogueListView.java @@ -9,4 +9,5 @@ public interface CatalogueListView extends BaseView { Intent getIntent(); void setSource(Source source); void setAdapter(EasyAdapter adapter); + void setScrollListener(); } diff --git a/app/src/main/java/eu/kanade/mangafeed/widget/EndlessScrollListener.java b/app/src/main/java/eu/kanade/mangafeed/widget/EndlessScrollListener.java new file mode 100644 index 000000000..95c2d3b4f --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/widget/EndlessScrollListener.java @@ -0,0 +1,69 @@ +package eu.kanade.mangafeed.widget; + +import android.widget.AbsListView; + +public abstract class EndlessScrollListener implements AbsListView.OnScrollListener { + // The minimum amount of items to have below your current scroll position + // before loading more. + private int visibleThreshold = 5; + // The current offset index of data you have loaded + private int currentPage = 0; + // The total number of items in the dataset after the last load + private int previousTotalItemCount = 0; + // True if we are still waiting for the last set of data to load. + private boolean loading = true; + // Sets the starting page index + private int startingPageIndex = 0; + + public EndlessScrollListener() { + } + + public EndlessScrollListener(int visibleThreshold) { + this.visibleThreshold = visibleThreshold; + } + + public EndlessScrollListener(int visibleThreshold, int startPage) { + this.visibleThreshold = visibleThreshold; + this.startingPageIndex = startPage; + this.currentPage = startPage; + } + + // This happens many times a second during a scroll, so be wary of the code you place here. + // We are given a few useful parameters to help us work out if we need to load some more data, + // but first we check if we are waiting for the previous load to finish. + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) + { + // If the total item count is zero and the previous isn't, assume the + // list is invalidated and should be reset back to initial state + if (totalItemCount < previousTotalItemCount) { + this.currentPage = this.startingPageIndex; + this.previousTotalItemCount = totalItemCount; + if (totalItemCount == 0) { this.loading = true; } + } + // If it’s still loading, we check to see if the dataset count has + // changed, if so we conclude it has finished loading and update the current page + // number and total item count. + if (loading && (totalItemCount > previousTotalItemCount)) { + loading = false; + previousTotalItemCount = totalItemCount; + currentPage++; + } + + // If it isn’t currently loading, we check to see if we have breached + // the visibleThreshold and need to reload more data. + // If we do need to reload some more data, we execute onLoadMore to fetch the data. + if (!loading && (totalItemCount - visibleItemCount)<=(firstVisibleItem + visibleThreshold)) { + loading = onLoadMore(currentPage + 1, totalItemCount); + } + } + + // Defines the process for actually loading more data based on page + // Returns true if more data is being loaded; returns false if there is no more data to load. + public abstract boolean onLoadMore(int page, int totalItemsCount); + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + // Don't take any action on changed + } +} \ No newline at end of file