mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-07 11:17:25 +01:00
Readers in Kotlin. Also fix #193
This commit is contained in:
parent
b2fe9d7d4d
commit
ff61282104
@ -97,13 +97,14 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
if (viewer != null)
|
if (viewer != null)
|
||||||
getPresenter().setCurrentPage(viewer.getCurrentPage());
|
getPresenter().setCurrentPage(viewer.getActivePage());
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
subscriptions.unsubscribe();
|
subscriptions.unsubscribe();
|
||||||
|
readerMenu.destroy();
|
||||||
viewer = null;
|
viewer = null;
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
@ -127,7 +128,7 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (viewer != null)
|
if (viewer != null)
|
||||||
getPresenter().setCurrentPage(viewer.getCurrentPage());
|
getPresenter().setCurrentPage(viewer.getActivePage());
|
||||||
getPresenter().onChapterLeft();
|
getPresenter().onChapterLeft();
|
||||||
|
|
||||||
int chapterToUpdate = getPresenter().getMangaSyncChapterToUpdate();
|
int chapterToUpdate = getPresenter().getMangaSyncChapterToUpdate();
|
||||||
@ -255,8 +256,8 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void gotoPageInCurrentChapter(int pageIndex) {
|
public void gotoPageInCurrentChapter(int pageIndex) {
|
||||||
Page requestedPage = viewer.getCurrentPage().getChapter().getPages().get(pageIndex);
|
Page requestedPage = viewer.getActivePage().getChapter().getPages().get(pageIndex);
|
||||||
viewer.setSelectedPage(requestedPage);
|
viewer.setActivePage(requestedPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCenterSingleTap() {
|
public void onCenterSingleTap() {
|
||||||
@ -264,7 +265,7 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void requestNextChapter() {
|
public void requestNextChapter() {
|
||||||
getPresenter().setCurrentPage(viewer.getCurrentPage());
|
getPresenter().setCurrentPage(viewer.getActivePage());
|
||||||
if (!getPresenter().loadNextChapter()) {
|
if (!getPresenter().loadNextChapter()) {
|
||||||
ToastUtil.showShort(this, R.string.no_next_chapter);
|
ToastUtil.showShort(this, R.string.no_next_chapter);
|
||||||
}
|
}
|
||||||
@ -272,7 +273,7 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void requestPreviousChapter() {
|
public void requestPreviousChapter() {
|
||||||
getPresenter().setCurrentPage(viewer.getCurrentPage());
|
getPresenter().setCurrentPage(viewer.getActivePage());
|
||||||
if (!getPresenter().loadPreviousChapter()) {
|
if (!getPresenter().loadPreviousChapter()) {
|
||||||
ToastUtil.showShort(this, R.string.no_previous_chapter);
|
ToastUtil.showShort(this, R.string.no_previous_chapter);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ import eu.kanade.tachiyomi.R;
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader;
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import rx.Subscription;
|
import rx.Subscription;
|
||||||
|
|
||||||
@ -116,6 +115,12 @@ public class ReaderMenu {
|
|||||||
showing = false;
|
showing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
if (settingsPopup != null) {
|
||||||
|
settingsPopup.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
activity.getMenuInflater().inflate(R.menu.reader, menu);
|
activity.getMenuInflater().inflate(R.menu.reader, menu);
|
||||||
nextChapterBtn = menu.findItem(R.id.action_next_chapter);
|
nextChapterBtn = menu.findItem(R.id.action_next_chapter);
|
||||||
@ -349,12 +354,12 @@ public class ReaderMenu {
|
|||||||
private void setDecoderInitial(int decoder) {
|
private void setDecoderInitial(int decoder) {
|
||||||
String initial;
|
String initial;
|
||||||
switch (decoder) {
|
switch (decoder) {
|
||||||
case BaseReader.SKIA_DECODER:
|
case 0:
|
||||||
initial = "S";
|
|
||||||
break;
|
|
||||||
case BaseReader.RAPID_DECODER:
|
|
||||||
initial = "R";
|
initial = "R";
|
||||||
break;
|
break;
|
||||||
|
case 1:
|
||||||
|
initial = "S";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
initial = "";
|
initial = "";
|
||||||
break;
|
break;
|
||||||
|
@ -1,130 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.base;
|
|
||||||
|
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder;
|
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder;
|
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.RapidImageRegionDecoder;
|
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageDecoder;
|
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
|
||||||
|
|
||||||
public abstract class BaseReader extends BaseFragment {
|
|
||||||
|
|
||||||
protected int currentPage;
|
|
||||||
protected List<Page> pages;
|
|
||||||
protected List<Chapter> chapters;
|
|
||||||
protected Class<? extends ImageRegionDecoder> regionDecoderClass;
|
|
||||||
protected Class<? extends ImageDecoder> bitmapDecoderClass;
|
|
||||||
|
|
||||||
private boolean hasRequestedNextChapter;
|
|
||||||
|
|
||||||
public static final int RAPID_DECODER = 0;
|
|
||||||
public static final int SKIA_DECODER = 1;
|
|
||||||
|
|
||||||
public void updatePageNumber() {
|
|
||||||
getReaderActivity().onPageChanged(getCurrentPage().getPageNumber(), getCurrentPage().getChapter().getPages().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Page getCurrentPage() {
|
|
||||||
return pages.get(currentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPageChanged(int position) {
|
|
||||||
Page oldPage = pages.get(currentPage);
|
|
||||||
Page newPage = pages.get(position);
|
|
||||||
newPage.getChapter().last_page_read = newPage.getPageNumber();
|
|
||||||
|
|
||||||
if (getReaderActivity().getPresenter().isSeamlessMode()) {
|
|
||||||
Chapter oldChapter = oldPage.getChapter();
|
|
||||||
Chapter newChapter = newPage.getChapter();
|
|
||||||
if (!hasRequestedNextChapter && position > pages.size() - 5) {
|
|
||||||
hasRequestedNextChapter = true;
|
|
||||||
getReaderActivity().getPresenter().appendNextChapter();
|
|
||||||
}
|
|
||||||
if (!oldChapter.id.equals(newChapter.id)) {
|
|
||||||
onChapterChanged(newPage.getChapter(), newPage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentPage = position;
|
|
||||||
updatePageNumber();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onChapterChanged(Chapter chapter, Page currentPage) {
|
|
||||||
getReaderActivity().onEnterChapter(chapter, currentPage.getPageNumber());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedPage(Page page) {
|
|
||||||
setSelectedPage(getPageIndex(page));
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPageIndex(Page search) {
|
|
||||||
// search for the index of a page in the current list without requiring them to be the same object
|
|
||||||
for (Page page : pages) {
|
|
||||||
if (page.getPageNumber() == search.getPageNumber() &&
|
|
||||||
page.getChapter().id.equals(search.getChapter().id)) {
|
|
||||||
return pages.indexOf(page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPageListReady(Chapter chapter, Page currentPage) {
|
|
||||||
if (chapters == null || !chapters.contains(chapter)) {
|
|
||||||
// if we reset the loaded page we also need to reset the loaded chapters
|
|
||||||
chapters = new ArrayList<>();
|
|
||||||
chapters.add(chapter);
|
|
||||||
onSetChapter(chapter, currentPage);
|
|
||||||
} else {
|
|
||||||
setSelectedPage(currentPage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPageListAppendReady(Chapter chapter) {
|
|
||||||
if (!chapters.contains(chapter)) {
|
|
||||||
hasRequestedNextChapter = false;
|
|
||||||
chapters.add(chapter);
|
|
||||||
onAppendChapter(chapter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void setSelectedPage(int pageNumber);
|
|
||||||
public abstract void onSetChapter(Chapter chapter, Page currentPage);
|
|
||||||
public abstract void onAppendChapter(Chapter chapter);
|
|
||||||
public abstract void moveToNext();
|
|
||||||
public abstract void moveToPrevious();
|
|
||||||
|
|
||||||
public void setDecoderClass(int value) {
|
|
||||||
switch (value) {
|
|
||||||
case RAPID_DECODER:
|
|
||||||
default:
|
|
||||||
regionDecoderClass = RapidImageRegionDecoder.class;
|
|
||||||
bitmapDecoderClass = SkiaImageDecoder.class;
|
|
||||||
// Using Skia because Rapid isn't stable. Rapid is still used for region decoding.
|
|
||||||
// https://github.com/inorichi/tachiyomi/issues/97
|
|
||||||
//bitmapDecoderClass = RapidImageDecoder.class;
|
|
||||||
break;
|
|
||||||
case SKIA_DECODER:
|
|
||||||
regionDecoderClass = SkiaImageRegionDecoder.class;
|
|
||||||
bitmapDecoderClass = SkiaImageDecoder.class;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<? extends ImageRegionDecoder> getRegionDecoderClass() {
|
|
||||||
return regionDecoderClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<? extends ImageDecoder> getBitmapDecoderClass() {
|
|
||||||
return bitmapDecoderClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReaderActivity getReaderActivity() {
|
|
||||||
return (ReaderActivity) getActivity();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,222 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.base
|
||||||
|
|
||||||
|
import com.davemorrissey.labs.subscaleview.decoder.*
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base reader containing the common data that can be used by its implementations. It does not
|
||||||
|
* contain any UI related action.
|
||||||
|
*/
|
||||||
|
abstract class BaseReader : BaseFragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Rapid decoder.
|
||||||
|
*/
|
||||||
|
const val RAPID_DECODER = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skia decoder.
|
||||||
|
*/
|
||||||
|
const val SKIA_DECODER = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of chapters added in the reader.
|
||||||
|
*/
|
||||||
|
private var chapters = ArrayList<Chapter>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of pages added in the reader. It can contain pages from more than one chapter.
|
||||||
|
*/
|
||||||
|
var pages: MutableList<Page> = ArrayList()
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current visible position of [pages].
|
||||||
|
*/
|
||||||
|
var currentPage: Int = 0
|
||||||
|
protected set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Region decoder class to use.
|
||||||
|
*/
|
||||||
|
lateinit var regionDecoderClass: Class<out ImageRegionDecoder>
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bitmap decoder class to use.
|
||||||
|
*/
|
||||||
|
lateinit var bitmapDecoderClass: Class<out ImageDecoder>
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the reader has requested to append a chapter. Used with seamless mode to avoid
|
||||||
|
* restarting requests when changing pages.
|
||||||
|
*/
|
||||||
|
private var hasRequestedNextChapter: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the reader activity with the active page.
|
||||||
|
*/
|
||||||
|
fun updatePageNumber() {
|
||||||
|
val activePage = getActivePage()
|
||||||
|
readerActivity.onPageChanged(activePage.pageNumber, activePage.chapter.pages.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the active page.
|
||||||
|
*/
|
||||||
|
fun getActivePage(): Page {
|
||||||
|
return pages[currentPage]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a page changes. Implementations must call this method.
|
||||||
|
*
|
||||||
|
* @param position the new current page.
|
||||||
|
*/
|
||||||
|
fun onPageChanged(position: Int) {
|
||||||
|
val oldPage = pages[currentPage]
|
||||||
|
val newPage = pages[position]
|
||||||
|
newPage.chapter.last_page_read = newPage.pageNumber
|
||||||
|
|
||||||
|
if (readerActivity.presenter.isSeamlessMode) {
|
||||||
|
val oldChapter = oldPage.chapter
|
||||||
|
val newChapter = newPage.chapter
|
||||||
|
if (!hasRequestedNextChapter && position > pages.size - 5) {
|
||||||
|
hasRequestedNextChapter = true
|
||||||
|
readerActivity.presenter.appendNextChapter()
|
||||||
|
}
|
||||||
|
if (oldChapter.id != newChapter.id) {
|
||||||
|
// Active chapter has changed.
|
||||||
|
readerActivity.onEnterChapter(newPage.chapter, newPage.pageNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentPage = position
|
||||||
|
updatePageNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the active page.
|
||||||
|
*
|
||||||
|
* @param page the page to display.
|
||||||
|
*/
|
||||||
|
fun setActivePage(page: Page) {
|
||||||
|
setActivePage(getPageIndex(page))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searchs for the index of a page in the current list without requiring them to be the same
|
||||||
|
* object.
|
||||||
|
*
|
||||||
|
* @param search the page to search.
|
||||||
|
* @return the index of the page in [pages] or 0 if it's not found.
|
||||||
|
*/
|
||||||
|
fun getPageIndex(search: Page): Int {
|
||||||
|
for ((index, page) in pages.withIndex()) {
|
||||||
|
if (page.pageNumber == search.pageNumber && page.chapter.id == search.chapter.id) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the presenter when the page list of a chapter is ready. This method is called
|
||||||
|
* on every [onResume], so we add some logic to avoid duplicating chapters.
|
||||||
|
*
|
||||||
|
* @param chapter the chapter to set.
|
||||||
|
* @param currentPage the initial page to display.
|
||||||
|
*/
|
||||||
|
fun onPageListReady(chapter: Chapter, currentPage: Page) {
|
||||||
|
if (!chapters.contains(chapter)) {
|
||||||
|
// if we reset the loaded page we also need to reset the loaded chapters
|
||||||
|
chapters = ArrayList<Chapter>()
|
||||||
|
chapters.add(chapter)
|
||||||
|
pages = ArrayList(chapter.pages)
|
||||||
|
onChapterSet(chapter, currentPage)
|
||||||
|
} else {
|
||||||
|
setActivePage(currentPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the presenter when the page list of a chapter to append is ready. This method is
|
||||||
|
* called on every [onResume], so we add some logic to avoid duplicating chapters.
|
||||||
|
*
|
||||||
|
* @param chapter the chapter to append.
|
||||||
|
*/
|
||||||
|
fun onPageListAppendReady(chapter: Chapter) {
|
||||||
|
if (!chapters.contains(chapter)) {
|
||||||
|
hasRequestedNextChapter = false
|
||||||
|
chapters.add(chapter)
|
||||||
|
pages.addAll(chapter.pages)
|
||||||
|
onChapterAppended(chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the active page.
|
||||||
|
*
|
||||||
|
* @param pageNumber the index of the page from [pages].
|
||||||
|
*/
|
||||||
|
abstract fun setActivePage(pageNumber: Int)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a new chapter is set in [BaseReader].
|
||||||
|
*
|
||||||
|
* @param chapter the chapter set.
|
||||||
|
* @param currentPage the initial page to display.
|
||||||
|
*/
|
||||||
|
abstract fun onChapterSet(chapter: Chapter, currentPage: Page)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a chapter is appended in [BaseReader].
|
||||||
|
*
|
||||||
|
* @param chapter the chapter appended.
|
||||||
|
*/
|
||||||
|
abstract fun onChapterAppended(chapter: Chapter)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves pages forward. Implementations decide how to move (by a page, by some distance...).
|
||||||
|
*/
|
||||||
|
abstract fun moveToNext()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves pages backward. Implementations decide how to move (by a page, by some distance...).
|
||||||
|
*/
|
||||||
|
abstract fun moveToPrevious()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the active decoder class.
|
||||||
|
*
|
||||||
|
* @param value the decoder class to use.
|
||||||
|
*/
|
||||||
|
fun setDecoderClass(value: Int) {
|
||||||
|
when (value) {
|
||||||
|
RAPID_DECODER -> {
|
||||||
|
// Using Skia because Rapid isn't stable. Rapid is still used for region decoding.
|
||||||
|
// https://github.com/inorichi/tachiyomi/issues/97
|
||||||
|
//bitmapDecoderClass = RapidImageDecoder.class;
|
||||||
|
regionDecoderClass = RapidImageRegionDecoder::class.java
|
||||||
|
bitmapDecoderClass = SkiaImageDecoder::class.java
|
||||||
|
}
|
||||||
|
SKIA_DECODER -> {
|
||||||
|
regionDecoderClass = SkiaImageRegionDecoder::class.java
|
||||||
|
bitmapDecoderClass = SkiaImageDecoder::class.java
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property to get the reader activity.
|
||||||
|
*/
|
||||||
|
val readerActivity: ReaderActivity
|
||||||
|
get() = activity as ReaderActivity
|
||||||
|
|
||||||
|
}
|
@ -1,67 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.base;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
|
||||||
import rx.functions.Action0;
|
|
||||||
|
|
||||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
|
||||||
|
|
||||||
public class PageDecodeErrorLayout extends LinearLayout {
|
|
||||||
|
|
||||||
private final int lightGreyColor;
|
|
||||||
private final int blackColor;
|
|
||||||
|
|
||||||
public PageDecodeErrorLayout(Context context) {
|
|
||||||
super(context);
|
|
||||||
setOrientation(LinearLayout.VERTICAL);
|
|
||||||
setGravity(Gravity.CENTER);
|
|
||||||
|
|
||||||
lightGreyColor = ContextCompat.getColor(context, R.color.light_grey);
|
|
||||||
blackColor = ContextCompat.getColor(context, R.color.primary_text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PageDecodeErrorLayout(Context context, Page page, int theme, Action0 retryListener) {
|
|
||||||
this(context);
|
|
||||||
|
|
||||||
TextView errorText = new TextView(context);
|
|
||||||
errorText.setGravity(Gravity.CENTER);
|
|
||||||
errorText.setText(R.string.decode_image_error);
|
|
||||||
errorText.setTextColor(theme == ReaderActivity.BLACK_THEME ? lightGreyColor : blackColor);
|
|
||||||
|
|
||||||
Button retryButton = new Button(context);
|
|
||||||
retryButton.setLayoutParams(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
|
||||||
retryButton.setText(R.string.action_retry);
|
|
||||||
retryButton.setOnClickListener((v) -> {
|
|
||||||
removeAllViews();
|
|
||||||
retryListener.call();
|
|
||||||
});
|
|
||||||
|
|
||||||
Button openInBrowserButton = new Button(context);
|
|
||||||
openInBrowserButton.setLayoutParams(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
|
||||||
openInBrowserButton.setText(R.string.action_open_in_browser);
|
|
||||||
openInBrowserButton.setOnClickListener((v) -> {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(page.getImageUrl()));
|
|
||||||
context.startActivity(intent);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (page.getImageUrl() == null) {
|
|
||||||
openInBrowserButton.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
addView(errorText);
|
|
||||||
addView(retryButton);
|
|
||||||
addView(openInBrowserButton);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,65 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.base
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
|
||||||
|
class PageDecodeErrorLayout(context: Context) : LinearLayout(context) {
|
||||||
|
|
||||||
|
private val lightGreyColor = ContextCompat.getColor(context, R.color.light_grey)
|
||||||
|
private val blackColor = ContextCompat.getColor(context, R.color.primary_text)
|
||||||
|
|
||||||
|
init {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
setGravity(Gravity.CENTER)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, page: Page, theme: Int, retryListener: () -> Unit) : this(context) {
|
||||||
|
|
||||||
|
// Error message.
|
||||||
|
TextView(context).apply {
|
||||||
|
gravity = Gravity.CENTER
|
||||||
|
setText(R.string.decode_image_error)
|
||||||
|
setTextColor(if (theme == ReaderActivity.BLACK_THEME) lightGreyColor else blackColor)
|
||||||
|
addView(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry button.
|
||||||
|
Button(context).apply {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
|
||||||
|
setText(R.string.action_retry)
|
||||||
|
setOnClickListener {
|
||||||
|
removeAllViews()
|
||||||
|
retryListener()
|
||||||
|
}
|
||||||
|
addView(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open in browser button.
|
||||||
|
Button(context).apply {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
|
||||||
|
setText(R.string.action_open_in_browser)
|
||||||
|
setOnClickListener { v ->
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(page.imageUrl))
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.imageUrl == null) {
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
addView(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
|
||||||
|
|
||||||
public interface OnChapterBoundariesOutListener {
|
|
||||||
void onFirstPageOutEvent();
|
|
||||||
void onLastPageOutEvent();
|
|
||||||
}
|
|
@ -0,0 +1,6 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||||
|
|
||||||
|
interface OnChapterBoundariesOutListener {
|
||||||
|
fun onFirstPageOutEvent()
|
||||||
|
fun onLastPageOutEvent()
|
||||||
|
}
|
@ -1,196 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
|
||||||
|
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader;
|
|
||||||
import rx.subscriptions.CompositeSubscription;
|
|
||||||
|
|
||||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
|
||||||
|
|
||||||
public abstract class PagerReader extends BaseReader {
|
|
||||||
|
|
||||||
protected PagerReaderAdapter adapter;
|
|
||||||
protected Pager pager;
|
|
||||||
protected GestureDetector gestureDetector;
|
|
||||||
|
|
||||||
protected boolean transitions;
|
|
||||||
protected CompositeSubscription subscriptions;
|
|
||||||
|
|
||||||
protected int scaleType = 1;
|
|
||||||
protected int zoomStart = 1;
|
|
||||||
|
|
||||||
public static final int ALIGN_AUTO = 1;
|
|
||||||
public static final int ALIGN_LEFT = 2;
|
|
||||||
public static final int ALIGN_RIGHT = 3;
|
|
||||||
public static final int ALIGN_CENTER = 4;
|
|
||||||
|
|
||||||
private static final float LEFT_REGION = 0.33f;
|
|
||||||
private static final float RIGHT_REGION = 0.66f;
|
|
||||||
|
|
||||||
protected void initializePager(Pager pager) {
|
|
||||||
this.pager = pager;
|
|
||||||
pager.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
|
||||||
pager.setOffscreenPageLimit(1);
|
|
||||||
pager.setId(R.id.view_pager);
|
|
||||||
pager.setOnChapterBoundariesOutListener(new OnChapterBoundariesOutListener() {
|
|
||||||
@Override
|
|
||||||
public void onFirstPageOutEvent() {
|
|
||||||
getReaderActivity().requestPreviousChapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLastPageOutEvent() {
|
|
||||||
getReaderActivity().requestNextChapter();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
gestureDetector = createGestureDetector();
|
|
||||||
|
|
||||||
adapter = new PagerReaderAdapter(getChildFragmentManager());
|
|
||||||
pager.setAdapter(adapter);
|
|
||||||
|
|
||||||
PreferencesHelper preferences = getReaderActivity().getPreferences();
|
|
||||||
subscriptions = new CompositeSubscription();
|
|
||||||
subscriptions.add(preferences.imageDecoder()
|
|
||||||
.asObservable()
|
|
||||||
.doOnNext(this::setDecoderClass)
|
|
||||||
.skip(1)
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.subscribe(v -> refreshPages()));
|
|
||||||
|
|
||||||
subscriptions.add(preferences.imageScaleType()
|
|
||||||
.asObservable()
|
|
||||||
.doOnNext(this::setImageScaleType)
|
|
||||||
.skip(1)
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.subscribe(v -> refreshPages()));
|
|
||||||
|
|
||||||
subscriptions.add(preferences.zoomStart()
|
|
||||||
.asObservable()
|
|
||||||
.doOnNext(this::setZoomStart)
|
|
||||||
.skip(1)
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.subscribe(v -> refreshPages()));
|
|
||||||
|
|
||||||
subscriptions.add(preferences.enableTransitions()
|
|
||||||
.asObservable()
|
|
||||||
.subscribe(value -> transitions = value));
|
|
||||||
|
|
||||||
setPages();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
subscriptions.unsubscribe();
|
|
||||||
super.onDestroyView();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected GestureDetector createGestureDetector() {
|
|
||||||
return new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
|
||||||
final float positionX = e.getX();
|
|
||||||
|
|
||||||
if (positionX < pager.getWidth() * LEFT_REGION) {
|
|
||||||
onLeftSideTap();
|
|
||||||
} else if (positionX > pager.getWidth() * RIGHT_REGION) {
|
|
||||||
onRightSideTap();
|
|
||||||
} else {
|
|
||||||
getReaderActivity().onCenterSingleTap();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSetChapter(Chapter chapter, Page currentPage) {
|
|
||||||
pages = new ArrayList<>(chapter.getPages());
|
|
||||||
this.currentPage = getPageIndex(currentPage); // we might have a new page object
|
|
||||||
|
|
||||||
// This method can be called before the view is created
|
|
||||||
if (pager != null) {
|
|
||||||
setPages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onAppendChapter(Chapter chapter) {
|
|
||||||
pages.addAll(chapter.getPages());
|
|
||||||
|
|
||||||
// This method can be called before the view is created
|
|
||||||
if (pager != null) {
|
|
||||||
adapter.setPages(pages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setPages() {
|
|
||||||
if (pages != null) {
|
|
||||||
pager.clearOnPageChangeListeners();
|
|
||||||
adapter.setPages(pages);
|
|
||||||
setSelectedPage(currentPage);
|
|
||||||
updatePageNumber();
|
|
||||||
pager.setOnPageChangeListener(this::onPageChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSelectedPage(int pageNumber) {
|
|
||||||
pager.setCurrentItem(pageNumber, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshPages() {
|
|
||||||
pager.setAdapter(adapter);
|
|
||||||
pager.setCurrentItem(currentPage, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onLeftSideTap() {
|
|
||||||
moveToPrevious();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onRightSideTap() {
|
|
||||||
moveToNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void moveToNext() {
|
|
||||||
if (pager.getCurrentItem() != pager.getAdapter().getCount() - 1) {
|
|
||||||
pager.setCurrentItem(pager.getCurrentItem() + 1, transitions);
|
|
||||||
} else {
|
|
||||||
getReaderActivity().requestNextChapter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void moveToPrevious() {
|
|
||||||
if (pager.getCurrentItem() != 0) {
|
|
||||||
pager.setCurrentItem(pager.getCurrentItem() - 1, transitions);
|
|
||||||
} else {
|
|
||||||
getReaderActivity().requestPreviousChapter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setImageScaleType(int scaleType) {
|
|
||||||
this.scaleType = scaleType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setZoomStart(int zoomStart) {
|
|
||||||
if (zoomStart == ALIGN_AUTO) {
|
|
||||||
if (this instanceof LeftToRightReader)
|
|
||||||
setZoomStart(ALIGN_LEFT);
|
|
||||||
else if (this instanceof RightToLeftReader)
|
|
||||||
setZoomStart(ALIGN_RIGHT);
|
|
||||||
else
|
|
||||||
setZoomStart(ALIGN_CENTER);
|
|
||||||
} else {
|
|
||||||
this.zoomStart = zoomStart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,287 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||||
|
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
|
||||||
|
import rx.subscriptions.CompositeSubscription
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a reader based on a ViewPager.
|
||||||
|
*/
|
||||||
|
abstract class PagerReader : BaseReader() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Zoom automatic alignment.
|
||||||
|
*/
|
||||||
|
const val ALIGN_AUTO = 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Align to left.
|
||||||
|
*/
|
||||||
|
const val ALIGN_LEFT = 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Align to right.
|
||||||
|
*/
|
||||||
|
const val ALIGN_RIGHT = 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Align to right.
|
||||||
|
*/
|
||||||
|
const val ALIGN_CENTER = 4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Left side region of the screen. Used for touch events.
|
||||||
|
*/
|
||||||
|
const val LEFT_REGION = 0.33f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Right side region of the screen. Used for touch events.
|
||||||
|
*/
|
||||||
|
const val RIGHT_REGION = 0.66f
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic interface of a ViewPager.
|
||||||
|
*/
|
||||||
|
lateinit var pager: Pager
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter of the pager.
|
||||||
|
*/
|
||||||
|
lateinit var adapter: PagerReaderAdapter
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gesture detector for touch events.
|
||||||
|
*/
|
||||||
|
val gestureDetector by lazy { createGestureDetector() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscriptions for reader settings.
|
||||||
|
*/
|
||||||
|
var subscriptions: CompositeSubscription? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether transitions are enabled or not.
|
||||||
|
*/
|
||||||
|
var transitions: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale type (fit width, fit screen, etc).
|
||||||
|
*/
|
||||||
|
var scaleType = 1
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zoom type (start position).
|
||||||
|
*/
|
||||||
|
var zoomType = 1
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the pager.
|
||||||
|
*
|
||||||
|
* @param pager the pager to initialize.
|
||||||
|
*/
|
||||||
|
protected fun initializePager(pager: Pager) {
|
||||||
|
adapter = PagerReaderAdapter(childFragmentManager)
|
||||||
|
|
||||||
|
this.pager = pager.apply {
|
||||||
|
setLayoutParams(ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))
|
||||||
|
setOffscreenPageLimit(1)
|
||||||
|
setId(R.id.view_pager)
|
||||||
|
setOnChapterBoundariesOutListener(object : OnChapterBoundariesOutListener {
|
||||||
|
override fun onFirstPageOutEvent() {
|
||||||
|
readerActivity.requestPreviousChapter()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLastPageOutEvent() {
|
||||||
|
readerActivity.requestNextChapter()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setOnPageChangeListener { onPageChanged(it) }
|
||||||
|
}
|
||||||
|
pager.adapter = adapter
|
||||||
|
|
||||||
|
subscriptions = CompositeSubscription().apply {
|
||||||
|
val preferences = readerActivity.preferences
|
||||||
|
|
||||||
|
add(preferences.imageDecoder()
|
||||||
|
.asObservable()
|
||||||
|
.doOnNext { setDecoderClass(it) }
|
||||||
|
.skip(1)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.subscribe { refreshAdapter() })
|
||||||
|
|
||||||
|
add(preferences.zoomStart()
|
||||||
|
.asObservable()
|
||||||
|
.doOnNext { setZoomStart(it) }
|
||||||
|
.skip(1)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.subscribe { refreshAdapter() })
|
||||||
|
|
||||||
|
add(preferences.imageScaleType()
|
||||||
|
.asObservable()
|
||||||
|
.doOnNext { scaleType = it }
|
||||||
|
.skip(1)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.subscribe { refreshAdapter() })
|
||||||
|
|
||||||
|
add(preferences.enableTransitions()
|
||||||
|
.asObservable()
|
||||||
|
.subscribe { transitions = it })
|
||||||
|
}
|
||||||
|
|
||||||
|
setPagesOnAdapter()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
pager.clearOnPageChangeListeners()
|
||||||
|
subscriptions?.unsubscribe()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the gesture detector for the pager.
|
||||||
|
*
|
||||||
|
* @return a gesture detector.
|
||||||
|
*/
|
||||||
|
protected fun createGestureDetector(): GestureDetector {
|
||||||
|
return GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
||||||
|
val positionX = e.x
|
||||||
|
|
||||||
|
if (positionX < pager.width * LEFT_REGION) {
|
||||||
|
onLeftSideTap()
|
||||||
|
} else if (positionX > pager.width * RIGHT_REGION) {
|
||||||
|
onRightSideTap()
|
||||||
|
} else {
|
||||||
|
readerActivity.onCenterSingleTap()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a new chapter is set in [BaseReader].
|
||||||
|
*
|
||||||
|
* @param chapter the chapter set.
|
||||||
|
* @param currentPage the initial page to display.
|
||||||
|
*/
|
||||||
|
override fun onChapterSet(chapter: Chapter, currentPage: Page) {
|
||||||
|
this.currentPage = getPageIndex(currentPage) // we might have a new page object
|
||||||
|
|
||||||
|
// Make sure the view is already initialized.
|
||||||
|
if (view != null) {
|
||||||
|
setPagesOnAdapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a chapter is appended in [BaseReader].
|
||||||
|
*
|
||||||
|
* @param chapter the chapter appended.
|
||||||
|
*/
|
||||||
|
override fun onChapterAppended(chapter: Chapter) {
|
||||||
|
// Make sure the view is already initialized.
|
||||||
|
if (view != null) {
|
||||||
|
adapter.pages = pages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the pages on the adapter.
|
||||||
|
*/
|
||||||
|
protected fun setPagesOnAdapter() {
|
||||||
|
if (pages.isNotEmpty()) {
|
||||||
|
adapter.pages = pages
|
||||||
|
setActivePage(currentPage)
|
||||||
|
updatePageNumber()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the active page.
|
||||||
|
*
|
||||||
|
* @param pageNumber the index of the page from [pages].
|
||||||
|
*/
|
||||||
|
override fun setActivePage(pageNumber: Int) {
|
||||||
|
pager.setCurrentItem(pageNumber, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the adapter.
|
||||||
|
*/
|
||||||
|
private fun refreshAdapter() {
|
||||||
|
pager.adapter = adapter
|
||||||
|
pager.setCurrentItem(currentPage, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the left side of the screen was clicked.
|
||||||
|
*/
|
||||||
|
protected open fun onLeftSideTap() {
|
||||||
|
moveToPrevious()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the right side of the screen was clicked.
|
||||||
|
*/
|
||||||
|
protected open fun onRightSideTap() {
|
||||||
|
moveToNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves to the next page or requests the next chapter if it's the last one.
|
||||||
|
*/
|
||||||
|
override fun moveToNext() {
|
||||||
|
if (pager.currentItem != pager.adapter.count - 1) {
|
||||||
|
pager.setCurrentItem(pager.currentItem + 1, transitions)
|
||||||
|
} else {
|
||||||
|
readerActivity.requestNextChapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves to the previous page or requests the previous chapter if it's the first one.
|
||||||
|
*/
|
||||||
|
override fun moveToPrevious() {
|
||||||
|
if (pager.currentItem != 0) {
|
||||||
|
pager.setCurrentItem(pager.currentItem - 1, transitions)
|
||||||
|
} else {
|
||||||
|
readerActivity.requestPreviousChapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the zoom start position.
|
||||||
|
*
|
||||||
|
* @param zoomStart the value stored in preferences.
|
||||||
|
*/
|
||||||
|
private fun setZoomStart(zoomStart: Int) {
|
||||||
|
if (zoomStart == ALIGN_AUTO) {
|
||||||
|
if (this is LeftToRightReader)
|
||||||
|
setZoomStart(ALIGN_LEFT)
|
||||||
|
else if (this is RightToLeftReader)
|
||||||
|
setZoomStart(ALIGN_RIGHT)
|
||||||
|
else
|
||||||
|
setZoomStart(ALIGN_CENTER)
|
||||||
|
} else {
|
||||||
|
zoomType = zoomStart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,60 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
|
||||||
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
|
||||||
|
|
||||||
public class PagerReaderAdapter extends FragmentStatePagerAdapter {
|
|
||||||
|
|
||||||
private List<Page> pages;
|
|
||||||
|
|
||||||
public PagerReaderAdapter(FragmentManager fragmentManager) {
|
|
||||||
super(fragmentManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return pages == null ? 0 : pages.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Fragment getItem(int position) {
|
|
||||||
return PagerReaderFragment.newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object instantiateItem(ViewGroup container, int position) {
|
|
||||||
PagerReaderFragment f = (PagerReaderFragment) super.instantiateItem(container, position);
|
|
||||||
f.setPage(pages.get(position));
|
|
||||||
f.setPosition(position);
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Page> getPages() {
|
|
||||||
return pages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPages(List<Page> pages) {
|
|
||||||
this.pages = pages;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemPosition(Object object) {
|
|
||||||
PagerReaderFragment f = (PagerReaderFragment) object;
|
|
||||||
int position = f.getPosition();
|
|
||||||
if (position >= 0 && position < getCount()) {
|
|
||||||
if (pages.get(position) == f.getPage()) {
|
|
||||||
return POSITION_UNCHANGED;
|
|
||||||
} else {
|
|
||||||
return POSITION_NONE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.getItemPosition(object);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,78 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment
|
||||||
|
import android.support.v4.app.FragmentManager
|
||||||
|
import android.support.v4.app.FragmentStatePagerAdapter
|
||||||
|
import android.support.v4.view.PagerAdapter
|
||||||
|
import android.view.ViewGroup
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter of pages for a ViewPager.
|
||||||
|
*
|
||||||
|
* @param fm the fragment manager.
|
||||||
|
*/
|
||||||
|
class PagerReaderAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pages stored in the adapter.
|
||||||
|
*/
|
||||||
|
var pages: List<Page>? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of pages.
|
||||||
|
*
|
||||||
|
* @return the number of pages or 0 if the list is null.
|
||||||
|
*/
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return pages?.size ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new fragment for the given position when it's called.
|
||||||
|
*
|
||||||
|
* @param position the position to instantiate.
|
||||||
|
* @return a fragment for the given position.
|
||||||
|
*/
|
||||||
|
override fun getItem(position: Int): Fragment {
|
||||||
|
return PagerReaderFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a fragment in the given position.
|
||||||
|
*
|
||||||
|
* @param container the parent view.
|
||||||
|
* @param position the position to instantiate.
|
||||||
|
* @return an instance of a fragment for the given position.
|
||||||
|
*/
|
||||||
|
override fun instantiateItem(container: ViewGroup, position: Int): Any {
|
||||||
|
val f = super.instantiateItem(container, position) as PagerReaderFragment
|
||||||
|
f.page = pages!![position]
|
||||||
|
f.position = position
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position of a given item.
|
||||||
|
*
|
||||||
|
* @param obj the item to find its position.
|
||||||
|
* @return the position for the item.
|
||||||
|
*/
|
||||||
|
override fun getItemPosition(obj: Any): Int {
|
||||||
|
val f = obj as PagerReaderFragment
|
||||||
|
val position = f.position
|
||||||
|
if (position >= 0 && position < count) {
|
||||||
|
if (pages!![position] === f.page) {
|
||||||
|
return PagerAdapter.POSITION_UNCHANGED
|
||||||
|
} else {
|
||||||
|
return PagerAdapter.POSITION_NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.getItemPosition(obj)
|
||||||
|
}
|
||||||
|
}
|
@ -1,270 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
|
||||||
|
|
||||||
import android.graphics.PointF;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource;
|
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical.VerticalReader;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
|
||||||
import rx.schedulers.Schedulers;
|
|
||||||
import rx.subjects.PublishSubject;
|
|
||||||
|
|
||||||
public class PagerReaderFragment extends BaseFragment {
|
|
||||||
|
|
||||||
@Bind(R.id.page_image_view) SubsamplingScaleImageView imageView;
|
|
||||||
@Bind(R.id.progress_container) LinearLayout progressContainer;
|
|
||||||
@Bind(R.id.progress) ProgressBar progressBar;
|
|
||||||
@Bind(R.id.progress_text) TextView progressText;
|
|
||||||
@Bind(R.id.retry_button) Button retryButton;
|
|
||||||
|
|
||||||
private Page page;
|
|
||||||
private Subscription progressSubscription;
|
|
||||||
private Subscription statusSubscription;
|
|
||||||
private int position = -1;
|
|
||||||
|
|
||||||
private int lightGreyColor;
|
|
||||||
private int blackColor;
|
|
||||||
|
|
||||||
public static PagerReaderFragment newInstance() {
|
|
||||||
return new PagerReaderFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.item_pager_reader, container, false);
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
ReaderActivity activity = getReaderActivity();
|
|
||||||
PagerReader parentFragment = (PagerReader) getParentFragment();
|
|
||||||
|
|
||||||
lightGreyColor = ContextCompat.getColor(getContext(), R.color.light_grey);
|
|
||||||
blackColor = ContextCompat.getColor(getContext(), R.color.primary_text);
|
|
||||||
|
|
||||||
if (activity.getReaderTheme() == ReaderActivity.BLACK_THEME) {
|
|
||||||
progressText.setTextColor(lightGreyColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentFragment instanceof RightToLeftReader) {
|
|
||||||
view.setRotation(-180);
|
|
||||||
}
|
|
||||||
|
|
||||||
imageView.setParallelLoadingEnabled(true);
|
|
||||||
imageView.setMaxBitmapDimensions(activity.getMaxBitmapSize());
|
|
||||||
imageView.setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED);
|
|
||||||
imageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE);
|
|
||||||
imageView.setMinimumScaleType(parentFragment.scaleType);
|
|
||||||
imageView.setMinimumDpi(50);
|
|
||||||
imageView.setRegionDecoderClass(parentFragment.getRegionDecoderClass());
|
|
||||||
imageView.setBitmapDecoderClass(parentFragment.getBitmapDecoderClass());
|
|
||||||
imageView.setVerticalScrollingParent(parentFragment instanceof VerticalReader);
|
|
||||||
imageView.setOnTouchListener((v, motionEvent) -> parentFragment.gestureDetector.onTouchEvent(motionEvent));
|
|
||||||
imageView.setOnImageEventListener(new SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
|
||||||
@Override
|
|
||||||
public void onReady() {
|
|
||||||
switch (parentFragment.zoomStart) {
|
|
||||||
case PagerReader.ALIGN_LEFT:
|
|
||||||
imageView.setScaleAndCenter(imageView.getScale(), new PointF(0, 0));
|
|
||||||
break;
|
|
||||||
case PagerReader.ALIGN_RIGHT:
|
|
||||||
imageView.setScaleAndCenter(imageView.getScale(), new PointF(imageView.getSWidth(), 0));
|
|
||||||
break;
|
|
||||||
case PagerReader.ALIGN_CENTER:
|
|
||||||
PointF center = imageView.getCenter();
|
|
||||||
center.y = 0;
|
|
||||||
imageView.setScaleAndCenter(imageView.getScale(), center);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onImageLoadError(Exception e) {
|
|
||||||
showImageDecodeError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
retryButton.setOnTouchListener((v, event) -> {
|
|
||||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
|
||||||
activity.getPresenter().retryPage(page);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
observeStatus();
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
unsubscribeProgress();
|
|
||||||
unsubscribeStatus();
|
|
||||||
imageView.setOnTouchListener(null);
|
|
||||||
imageView.setOnImageEventListener(null);
|
|
||||||
ButterKnife.unbind(this);
|
|
||||||
super.onDestroyView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPage(Page page) {
|
|
||||||
this.page = page;
|
|
||||||
|
|
||||||
// This method can be called before the view is created
|
|
||||||
if (imageView != null) {
|
|
||||||
observeStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPosition(int position) {
|
|
||||||
this.position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showImage() {
|
|
||||||
if (page == null || page.getImagePath() == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
File imagePath = new File(page.getImagePath());
|
|
||||||
if (imagePath.exists()) {
|
|
||||||
imageView.setImage(ImageSource.uri(page.getImagePath()));
|
|
||||||
progressContainer.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
page.setStatus(Page.ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showDownloading() {
|
|
||||||
progressContainer.setVisibility(View.VISIBLE);
|
|
||||||
progressText.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showLoading() {
|
|
||||||
progressContainer.setVisibility(View.VISIBLE);
|
|
||||||
progressText.setVisibility(View.VISIBLE);
|
|
||||||
progressText.setText(R.string.downloading);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showError() {
|
|
||||||
progressContainer.setVisibility(View.GONE);
|
|
||||||
retryButton.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideError() {
|
|
||||||
retryButton.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showImageDecodeError() {
|
|
||||||
ViewGroup view = (ViewGroup) getView();
|
|
||||||
if (view == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LinearLayout errorLayout = new PageDecodeErrorLayout(getContext(), page,
|
|
||||||
getReaderActivity().getReaderTheme(),
|
|
||||||
() -> getReaderActivity().getPresenter().retryPage(page));
|
|
||||||
|
|
||||||
view.addView(errorLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processStatus(int status) {
|
|
||||||
switch (status) {
|
|
||||||
case Page.QUEUE:
|
|
||||||
hideError();
|
|
||||||
break;
|
|
||||||
case Page.LOAD_PAGE:
|
|
||||||
showLoading();
|
|
||||||
break;
|
|
||||||
case Page.DOWNLOAD_IMAGE:
|
|
||||||
observeProgress();
|
|
||||||
showDownloading();
|
|
||||||
break;
|
|
||||||
case Page.READY:
|
|
||||||
showImage();
|
|
||||||
unsubscribeProgress();
|
|
||||||
break;
|
|
||||||
case Page.ERROR:
|
|
||||||
showError();
|
|
||||||
unsubscribeProgress();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void observeStatus() {
|
|
||||||
if (page == null || statusSubscription != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
PublishSubject<Integer> statusSubject = PublishSubject.create();
|
|
||||||
page.setStatusSubject(statusSubject);
|
|
||||||
|
|
||||||
statusSubscription = statusSubject
|
|
||||||
.startWith(page.getStatus())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(this::processStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void observeProgress() {
|
|
||||||
if (progressSubscription != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
final AtomicInteger currentValue = new AtomicInteger(-1);
|
|
||||||
|
|
||||||
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS, Schedulers.newThread())
|
|
||||||
.onBackpressureLatest()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(tick -> {
|
|
||||||
// Refresh UI only if progress change
|
|
||||||
if (page.getProgress() != currentValue.get()) {
|
|
||||||
currentValue.set(page.getProgress());
|
|
||||||
progressText.setText(getString(R.string.download_progress, page.getProgress()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unsubscribeStatus() {
|
|
||||||
if (statusSubscription != null) {
|
|
||||||
page.setStatusSubject(null);
|
|
||||||
statusSubscription.unsubscribe();
|
|
||||||
statusSubscription = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unsubscribeProgress() {
|
|
||||||
if (progressSubscription != null) {
|
|
||||||
progressSubscription.unsubscribe();
|
|
||||||
progressSubscription = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Page getPage() {
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPosition() {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReaderActivity getReaderActivity() {
|
|
||||||
return (ReaderActivity) getActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,294 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||||
|
|
||||||
|
import android.graphics.PointF
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical.VerticalReader
|
||||||
|
import kotlinx.android.synthetic.main.chapter_image.*
|
||||||
|
import kotlinx.android.synthetic.main.item_pager_reader.*
|
||||||
|
import rx.Observable
|
||||||
|
import rx.Subscription
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import rx.subjects.PublishSubject
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for a single page of the ViewPager reader.
|
||||||
|
* All the elements from the layout file "item_pager_reader" are available in this class.
|
||||||
|
*/
|
||||||
|
class PagerReaderFragment : BaseFragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Creates a new instance of this fragment.
|
||||||
|
*
|
||||||
|
* @return a new instance of [PagerReaderFragment].
|
||||||
|
*/
|
||||||
|
fun newInstance(): PagerReaderFragment {
|
||||||
|
return PagerReaderFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page of a chapter.
|
||||||
|
*/
|
||||||
|
var page: Page? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
// Observe status if the view is initialized
|
||||||
|
if (view != null) {
|
||||||
|
observeStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position of the fragment in the adapter.
|
||||||
|
*/
|
||||||
|
var position = -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription for progress changes of the page.
|
||||||
|
*/
|
||||||
|
private var progressSubscription: Subscription? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription for status changes of the page.
|
||||||
|
*/
|
||||||
|
private var statusSubscription: Subscription? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text color for black theme.
|
||||||
|
*/
|
||||||
|
private val lightGreyColor by lazy { ContextCompat.getColor(context, R.color.light_grey) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text color for white theme.
|
||||||
|
*/
|
||||||
|
private val blackColor by lazy { ContextCompat.getColor(context, R.color.primary_text) }
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.item_pager_reader, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||||
|
if (readerActivity.readerTheme == ReaderActivity.BLACK_THEME) {
|
||||||
|
progress_text.setTextColor(lightGreyColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pagerReader is RightToLeftReader) {
|
||||||
|
view.rotation = -180f
|
||||||
|
}
|
||||||
|
|
||||||
|
with(image_view) {
|
||||||
|
setParallelLoadingEnabled(true)
|
||||||
|
setMaxBitmapDimensions(readerActivity.maxBitmapSize)
|
||||||
|
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED)
|
||||||
|
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
|
||||||
|
setMinimumScaleType(pagerReader.scaleType)
|
||||||
|
setMinimumDpi(50)
|
||||||
|
setRegionDecoderClass(pagerReader.regionDecoderClass)
|
||||||
|
setBitmapDecoderClass(pagerReader.bitmapDecoderClass)
|
||||||
|
setVerticalScrollingParent(pagerReader is VerticalReader)
|
||||||
|
setOnTouchListener { v, motionEvent -> pagerReader.gestureDetector.onTouchEvent(motionEvent) }
|
||||||
|
setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
||||||
|
override fun onReady() {
|
||||||
|
when (pagerReader.zoomType) {
|
||||||
|
PagerReader.ALIGN_LEFT -> setScaleAndCenter(scale, PointF(0f, 0f))
|
||||||
|
PagerReader.ALIGN_RIGHT -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0f))
|
||||||
|
PagerReader.ALIGN_CENTER -> {
|
||||||
|
val newCenter = center
|
||||||
|
newCenter.y = 0f
|
||||||
|
setScaleAndCenter(scale, newCenter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onImageLoadError(e: Exception) {
|
||||||
|
onImageDecodeError()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_button.setOnTouchListener { v, event ->
|
||||||
|
if (event.action == MotionEvent.ACTION_UP) {
|
||||||
|
readerActivity.presenter.retryPage(page)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
observeStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
unsubscribeProgress()
|
||||||
|
unsubscribeStatus()
|
||||||
|
image_view.setOnTouchListener(null)
|
||||||
|
image_view.setOnImageEventListener(null)
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the status of the page and notify the changes.
|
||||||
|
*
|
||||||
|
* @see processStatus
|
||||||
|
*/
|
||||||
|
private fun observeStatus() {
|
||||||
|
page?.let { page ->
|
||||||
|
val statusSubject = PublishSubject.create<Int>()
|
||||||
|
page.setStatusSubject(statusSubject)
|
||||||
|
|
||||||
|
statusSubscription?.unsubscribe()
|
||||||
|
statusSubscription = statusSubject.startWith(page.status)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { processStatus(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the progress of the page and updates view.
|
||||||
|
*/
|
||||||
|
private fun observeProgress() {
|
||||||
|
val currentValue = AtomicInteger(-1)
|
||||||
|
|
||||||
|
progressSubscription?.unsubscribe()
|
||||||
|
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS, Schedulers.newThread())
|
||||||
|
.onBackpressureLatest()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe {
|
||||||
|
// Refresh UI only if progress change
|
||||||
|
if (page?.progress != currentValue.get()) {
|
||||||
|
currentValue.set(page?.progress ?: 0)
|
||||||
|
progress_text.text = getString(R.string.download_progress, currentValue.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the status of the page changes.
|
||||||
|
*
|
||||||
|
* @param status the new status of the page.
|
||||||
|
*/
|
||||||
|
private fun processStatus(status: Int) {
|
||||||
|
when (status) {
|
||||||
|
Page.QUEUE -> hideError()
|
||||||
|
Page.LOAD_PAGE -> onLoading()
|
||||||
|
Page.DOWNLOAD_IMAGE -> {
|
||||||
|
observeProgress()
|
||||||
|
onDownloading()
|
||||||
|
}
|
||||||
|
Page.READY -> {
|
||||||
|
onReady()
|
||||||
|
unsubscribeProgress()
|
||||||
|
}
|
||||||
|
Page.ERROR -> {
|
||||||
|
onError()
|
||||||
|
unsubscribeProgress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes from the status subscription.
|
||||||
|
*/
|
||||||
|
private fun unsubscribeStatus() {
|
||||||
|
page?.setStatusSubject(null)
|
||||||
|
statusSubscription?.unsubscribe()
|
||||||
|
statusSubscription = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes from the progress subscription.
|
||||||
|
*/
|
||||||
|
private fun unsubscribeProgress() {
|
||||||
|
progressSubscription?.unsubscribe()
|
||||||
|
progressSubscription = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the page is loading.
|
||||||
|
*/
|
||||||
|
private fun onLoading() {
|
||||||
|
progress_container.visibility = View.VISIBLE
|
||||||
|
progress_text.visibility = View.VISIBLE
|
||||||
|
progress_text.setText(R.string.downloading)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the page is downloading.
|
||||||
|
*/
|
||||||
|
private fun onDownloading() {
|
||||||
|
progress_container.visibility = View.VISIBLE
|
||||||
|
progress_text.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the page is ready.
|
||||||
|
*/
|
||||||
|
private fun onReady() {
|
||||||
|
page?.imagePath?.let { path ->
|
||||||
|
if (File(path).exists()) {
|
||||||
|
image_view.setImage(ImageSource.uri(path))
|
||||||
|
progress_container.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
page?.status = Page.ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the page has an error.
|
||||||
|
*/
|
||||||
|
private fun onError() {
|
||||||
|
progress_container.visibility = View.GONE
|
||||||
|
retry_button.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the error layout.
|
||||||
|
*/
|
||||||
|
private fun hideError() {
|
||||||
|
retry_button.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an image fails to decode.
|
||||||
|
*/
|
||||||
|
private fun onImageDecodeError() {
|
||||||
|
val view = view as? ViewGroup ?: return
|
||||||
|
|
||||||
|
page?.let { page ->
|
||||||
|
val errorLayout = PageDecodeErrorLayout(context, page, readerActivity.readerTheme,
|
||||||
|
{ readerActivity.presenter.retryPage(page) })
|
||||||
|
|
||||||
|
view.addView(errorLayout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property to get the reader activity.
|
||||||
|
*/
|
||||||
|
private val readerActivity: ReaderActivity
|
||||||
|
get() = activity as ReaderActivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property to get the pager reader.
|
||||||
|
*/
|
||||||
|
private val pagerReader: PagerReader
|
||||||
|
get() = parentFragment as PagerReader
|
||||||
|
|
||||||
|
}
|
@ -1,87 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.v4.view.ViewPager;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.OnChapterBoundariesOutListener;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
|
|
||||||
public class HorizontalPager extends ViewPager implements Pager {
|
|
||||||
|
|
||||||
private OnChapterBoundariesOutListener onChapterBoundariesOutListener;
|
|
||||||
|
|
||||||
private static final float SWIPE_TOLERANCE = 0.25f;
|
|
||||||
private float startDragX;
|
|
||||||
|
|
||||||
public HorizontalPager(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
||||||
try {
|
|
||||||
if ((ev.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
|
|
||||||
if (getCurrentItem() == 0 || getCurrentItem() == getAdapter().getCount() - 1) {
|
|
||||||
startDragX = ev.getX();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onInterceptTouchEvent(ev);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent ev) {
|
|
||||||
try {
|
|
||||||
if (onChapterBoundariesOutListener != null) {
|
|
||||||
if (getCurrentItem() == 0) {
|
|
||||||
if ((ev.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
|
|
||||||
float displacement = ev.getX() - startDragX;
|
|
||||||
|
|
||||||
if (ev.getX() > startDragX && displacement > getWidth() * SWIPE_TOLERANCE) {
|
|
||||||
onChapterBoundariesOutListener.onFirstPageOutEvent();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
startDragX = 0;
|
|
||||||
}
|
|
||||||
} else if (getCurrentItem() == getAdapter().getCount() - 1) {
|
|
||||||
if ((ev.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
|
|
||||||
float displacement = startDragX - ev.getX();
|
|
||||||
|
|
||||||
if (ev.getX() < startDragX && displacement > getWidth() * SWIPE_TOLERANCE) {
|
|
||||||
onChapterBoundariesOutListener.onLastPageOutEvent();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
startDragX = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onTouchEvent(ev);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener) {
|
|
||||||
onChapterBoundariesOutListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnPageChangeListener(Action1<Integer> function) {
|
|
||||||
addOnPageChangeListener(new SimpleOnPageChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onPageSelected(int position) {
|
|
||||||
function.call(position);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,86 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.v4.view.ViewPager
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.OnChapterBoundariesOutListener
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager
|
||||||
|
import rx.functions.Action1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a [ViewPager] to add custom behavior on touch events.
|
||||||
|
*/
|
||||||
|
class HorizontalPager(context: Context) : ViewPager(context), Pager {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val SWIPE_TOLERANCE = 0.25f
|
||||||
|
}
|
||||||
|
|
||||||
|
private var onChapterBoundariesOutListener: OnChapterBoundariesOutListener? = null
|
||||||
|
|
||||||
|
private var startDragX: Float = 0f
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
try {
|
||||||
|
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_DOWN) {
|
||||||
|
if (currentItem == 0 || currentItem == adapter.count - 1) {
|
||||||
|
startDragX = ev.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onInterceptTouchEvent(ev)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
try {
|
||||||
|
onChapterBoundariesOutListener?.let { listener ->
|
||||||
|
if (currentItem == 0) {
|
||||||
|
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_UP) {
|
||||||
|
val displacement = ev.x - startDragX
|
||||||
|
|
||||||
|
if (ev.x > startDragX && displacement > width * SWIPE_TOLERANCE) {
|
||||||
|
listener.onFirstPageOutEvent()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
startDragX = 0f
|
||||||
|
}
|
||||||
|
} else if (currentItem == adapter.count - 1) {
|
||||||
|
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_UP) {
|
||||||
|
val displacement = startDragX - ev.x
|
||||||
|
|
||||||
|
if (ev.x < startDragX && displacement > width * SWIPE_TOLERANCE) {
|
||||||
|
listener.onLastPageOutEvent()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
startDragX = 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onTouchEvent(ev)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOnChapterBoundariesOutListener(listener: OnChapterBoundariesOutListener) {
|
||||||
|
onChapterBoundariesOutListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOnPageChangeListener(func: Action1<Int>) {
|
||||||
|
addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
func.call(position)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader;
|
|
||||||
|
|
||||||
public class LeftToRightReader extends PagerReader {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
|
||||||
HorizontalPager pager = new HorizontalPager(getActivity());
|
|
||||||
initializePager(pager);
|
|
||||||
return pager;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,19 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Left to Right reader.
|
||||||
|
*/
|
||||||
|
class LeftToRightReader : PagerReader() {
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
|
return HorizontalPager(activity).apply { initializePager(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader;
|
|
||||||
|
|
||||||
public class RightToLeftReader extends PagerReader {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
|
||||||
HorizontalPager pager = new HorizontalPager(getActivity());
|
|
||||||
pager.setRotation(180);
|
|
||||||
initializePager(pager);
|
|
||||||
return pager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onLeftSideTap() {
|
|
||||||
moveToNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRightSideTap() {
|
|
||||||
moveToPrevious();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,30 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Right to Left reader.
|
||||||
|
*/
|
||||||
|
class RightToLeftReader : PagerReader() {
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
|
return HorizontalPager(activity).apply {
|
||||||
|
rotation = 180f
|
||||||
|
initializePager(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLeftSideTap() {
|
||||||
|
moveToNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRightSideTap() {
|
||||||
|
moveToPrevious()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,86 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.OnChapterBoundariesOutListener;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
|
|
||||||
public class VerticalPager extends VerticalViewPagerImpl implements Pager {
|
|
||||||
|
|
||||||
private OnChapterBoundariesOutListener onChapterBoundariesOutListener;
|
|
||||||
|
|
||||||
private static final float SWIPE_TOLERANCE = 0.25f;
|
|
||||||
private float startDragY;
|
|
||||||
|
|
||||||
public VerticalPager(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
||||||
try {
|
|
||||||
if ((ev.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
|
|
||||||
if (getCurrentItem() == 0 || getCurrentItem() == getAdapter().getCount() - 1) {
|
|
||||||
startDragY = ev.getY();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onInterceptTouchEvent(ev);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent ev) {
|
|
||||||
try {
|
|
||||||
if (onChapterBoundariesOutListener != null) {
|
|
||||||
if (getCurrentItem() == 0) {
|
|
||||||
if ((ev.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
|
|
||||||
float displacement = ev.getY() - startDragY;
|
|
||||||
|
|
||||||
if (ev.getY() > startDragY && displacement > getHeight() * SWIPE_TOLERANCE) {
|
|
||||||
onChapterBoundariesOutListener.onFirstPageOutEvent();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
startDragY = 0;
|
|
||||||
}
|
|
||||||
} else if (getCurrentItem() == getAdapter().getCount() - 1) {
|
|
||||||
if ((ev.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
|
|
||||||
float displacement = startDragY - ev.getY();
|
|
||||||
|
|
||||||
if (ev.getY() < startDragY && displacement > getHeight() * SWIPE_TOLERANCE) {
|
|
||||||
onChapterBoundariesOutListener.onLastPageOutEvent();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
startDragY = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onTouchEvent(ev);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener) {
|
|
||||||
onChapterBoundariesOutListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnPageChangeListener(Action1<Integer> function) {
|
|
||||||
addOnPageChangeListener(new SimpleOnPageChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onPageSelected(int position) {
|
|
||||||
function.call(position);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,84 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.OnChapterBoundariesOutListener
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager
|
||||||
|
import rx.functions.Action1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a [VerticalViewPagerImpl] to add custom behavior on touch events.
|
||||||
|
*/
|
||||||
|
class VerticalPager(context: Context) : VerticalViewPagerImpl(context), Pager {
|
||||||
|
|
||||||
|
private var onChapterBoundariesOutListener: OnChapterBoundariesOutListener? = null
|
||||||
|
private var startDragY: Float = 0.toFloat()
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
try {
|
||||||
|
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_DOWN) {
|
||||||
|
if (currentItem == 0 || currentItem == adapter.count - 1) {
|
||||||
|
startDragY = ev.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onInterceptTouchEvent(ev)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
try {
|
||||||
|
onChapterBoundariesOutListener?.let { listener ->
|
||||||
|
if (currentItem == 0) {
|
||||||
|
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_UP) {
|
||||||
|
val displacement = ev.y - startDragY
|
||||||
|
|
||||||
|
if (ev.y > startDragY && displacement > height * SWIPE_TOLERANCE) {
|
||||||
|
listener.onFirstPageOutEvent()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
startDragY = 0f
|
||||||
|
}
|
||||||
|
} else if (currentItem == adapter.count - 1) {
|
||||||
|
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_UP) {
|
||||||
|
val displacement = startDragY - ev.y
|
||||||
|
|
||||||
|
if (ev.y < startDragY && displacement > height * SWIPE_TOLERANCE) {
|
||||||
|
listener.onLastPageOutEvent()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
startDragY = 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onTouchEvent(ev)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOnChapterBoundariesOutListener(listener: OnChapterBoundariesOutListener) {
|
||||||
|
onChapterBoundariesOutListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOnPageChangeListener(func: Action1<Int>) {
|
||||||
|
addOnPageChangeListener(object : VerticalViewPagerImpl.SimpleOnPageChangeListener() {
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
func.call(position)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val SWIPE_TOLERANCE = 0.25f
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader;
|
|
||||||
|
|
||||||
public class VerticalReader extends PagerReader {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
|
||||||
VerticalPager pager = new VerticalPager(getActivity());
|
|
||||||
initializePager(pager);
|
|
||||||
return pager;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,19 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertical reader.
|
||||||
|
*/
|
||||||
|
class VerticalReader : PagerReader() {
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
|
return VerticalPager(activity).apply { initializePager(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,72 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon;
|
|
||||||
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
|
||||||
|
|
||||||
public class WebtoonAdapter extends RecyclerView.Adapter<WebtoonHolder> {
|
|
||||||
|
|
||||||
private WebtoonReader fragment;
|
|
||||||
private List<Page> pages;
|
|
||||||
private View.OnTouchListener touchListener;
|
|
||||||
|
|
||||||
public WebtoonAdapter(WebtoonReader fragment) {
|
|
||||||
this.fragment = fragment;
|
|
||||||
pages = new ArrayList<>();
|
|
||||||
touchListener = (v, event) -> fragment.gestureDetector.onTouchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Page getItem(int position) {
|
|
||||||
return pages.get(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WebtoonHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
LayoutInflater inflater = fragment.getActivity().getLayoutInflater();
|
|
||||||
View v = inflater.inflate(R.layout.item_webtoon_reader, parent, false);
|
|
||||||
return new WebtoonHolder(v, this, touchListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(WebtoonHolder holder, int position) {
|
|
||||||
final Page page = getItem(position);
|
|
||||||
holder.onSetValues(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return pages.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPages(List<Page> pages) {
|
|
||||||
this.pages = pages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
if (pages != null) {
|
|
||||||
pages.clear();
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void retryPage(Page page) {
|
|
||||||
fragment.getReaderActivity().getPresenter().retryPage(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebtoonReader getReader() {
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReaderActivity getReaderActivity() {
|
|
||||||
return (ReaderActivity) fragment.getActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,78 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter of pages for a RecyclerView.
|
||||||
|
*
|
||||||
|
* @param fragment the fragment containing this adapter.
|
||||||
|
*/
|
||||||
|
class WebtoonAdapter(val fragment: WebtoonReader) : RecyclerView.Adapter<WebtoonHolder>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pages stored in the adapter.
|
||||||
|
*/
|
||||||
|
var pages: List<Page>? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Touch listener for images in holders.
|
||||||
|
*/
|
||||||
|
val touchListener = View.OnTouchListener { v, ev -> fragment.gestureDetector.onTouchEvent(ev) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of pages.
|
||||||
|
*
|
||||||
|
* @return the number of pages or 0 if the list is null.
|
||||||
|
*/
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return pages?.size ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a page given the position.
|
||||||
|
*
|
||||||
|
* @param position the position of the page.
|
||||||
|
* @return the page.
|
||||||
|
*/
|
||||||
|
fun getItem(position: Int): Page {
|
||||||
|
return pages!![position]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new view holder.
|
||||||
|
*
|
||||||
|
* @param parent the parent view.
|
||||||
|
* @param viewType the type of the holder.
|
||||||
|
* @return a new view holder for a manga.
|
||||||
|
*/
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WebtoonHolder {
|
||||||
|
val v = parent.inflate(R.layout.item_webtoon_reader)
|
||||||
|
return WebtoonHolder(v, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a holder with a new position.
|
||||||
|
*
|
||||||
|
* @param holder the holder to bind.
|
||||||
|
* @param position the position to bind.
|
||||||
|
*/
|
||||||
|
override fun onBindViewHolder(holder: WebtoonHolder, position: Int) {
|
||||||
|
val page = getItem(position)
|
||||||
|
holder.onSetValues(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recycles the view holder.
|
||||||
|
*
|
||||||
|
* @param holder the holder to recycle.
|
||||||
|
*/
|
||||||
|
override fun onViewRecycled(holder: WebtoonHolder) {
|
||||||
|
holder.unsubscribeStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,135 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon;
|
|
||||||
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
|
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource;
|
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
|
||||||
|
|
||||||
public class WebtoonHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
@Bind(R.id.page_image_view) SubsamplingScaleImageView imageView;
|
|
||||||
@Bind(R.id.frame_container) ViewGroup container;
|
|
||||||
@Bind(R.id.progress) ProgressBar progressBar;
|
|
||||||
@Bind(R.id.retry_button) Button retryButton;
|
|
||||||
|
|
||||||
private Page page;
|
|
||||||
private WebtoonAdapter adapter;
|
|
||||||
|
|
||||||
public WebtoonHolder(View view, WebtoonAdapter adapter, View.OnTouchListener touchListener) {
|
|
||||||
super(view);
|
|
||||||
this.adapter = adapter;
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
imageView.setParallelLoadingEnabled(true);
|
|
||||||
imageView.setMaxBitmapDimensions(adapter.getReaderActivity().getMaxBitmapSize());
|
|
||||||
imageView.setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED);
|
|
||||||
imageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE);
|
|
||||||
imageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH);
|
|
||||||
imageView.setMaxScale(10);
|
|
||||||
imageView.setRegionDecoderClass(adapter.getReader().getRegionDecoderClass());
|
|
||||||
imageView.setBitmapDecoderClass(adapter.getReader().getBitmapDecoderClass());
|
|
||||||
imageView.setVerticalScrollingParent(true);
|
|
||||||
imageView.setOnTouchListener(touchListener);
|
|
||||||
imageView.setOnImageEventListener(new SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
|
||||||
@Override
|
|
||||||
public void onImageLoaded() {
|
|
||||||
// When the image is loaded, reset the minimum height to avoid gaps
|
|
||||||
container.setMinimumHeight(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Avoid to create a lot of view holders taking twice the screen height,
|
|
||||||
// saving memory and a possible OOM. When the first image is loaded in this holder,
|
|
||||||
// the minimum size will be removed.
|
|
||||||
// Doing this we get sequential holder instantiation.
|
|
||||||
container.setMinimumHeight(view.getResources().getDisplayMetrics().heightPixels * 2);
|
|
||||||
|
|
||||||
// Leave some space between progress bars
|
|
||||||
progressBar.setMinimumHeight(300);
|
|
||||||
|
|
||||||
container.setOnTouchListener(touchListener);
|
|
||||||
retryButton.setOnTouchListener((v, event) -> {
|
|
||||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
|
||||||
adapter.retryPage(page);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSetValues(Page page) {
|
|
||||||
this.page = page;
|
|
||||||
switch (page.getStatus()) {
|
|
||||||
case Page.QUEUE:
|
|
||||||
onQueue();
|
|
||||||
break;
|
|
||||||
case Page.LOAD_PAGE:
|
|
||||||
onLoading();
|
|
||||||
break;
|
|
||||||
case Page.DOWNLOAD_IMAGE:
|
|
||||||
onLoading();
|
|
||||||
break;
|
|
||||||
case Page.READY:
|
|
||||||
onReady();
|
|
||||||
break;
|
|
||||||
case Page.ERROR:
|
|
||||||
onError();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onLoading() {
|
|
||||||
setErrorButtonVisible(false);
|
|
||||||
setImageVisible(false);
|
|
||||||
setProgressVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onReady() {
|
|
||||||
setErrorButtonVisible(false);
|
|
||||||
setProgressVisible(false);
|
|
||||||
setImageVisible(true);
|
|
||||||
|
|
||||||
File imagePath = new File(page.getImagePath());
|
|
||||||
if (imagePath.exists()) {
|
|
||||||
imageView.setImage(ImageSource.uri(page.getImagePath()));
|
|
||||||
} else {
|
|
||||||
page.setStatus(Page.ERROR);
|
|
||||||
onError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onError() {
|
|
||||||
setImageVisible(false);
|
|
||||||
setProgressVisible(false);
|
|
||||||
setErrorButtonVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onQueue() {
|
|
||||||
setImageVisible(false);
|
|
||||||
setErrorButtonVisible(false);
|
|
||||||
setProgressVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setProgressVisible(boolean visible) {
|
|
||||||
progressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setImageVisible(boolean visible) {
|
|
||||||
imageView.setVisibility(visible ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setErrorButtonVisible(boolean visible) {
|
|
||||||
retryButton.setVisibility(visible ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,240 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout
|
||||||
|
import kotlinx.android.synthetic.main.chapter_image.view.*
|
||||||
|
import kotlinx.android.synthetic.main.item_webtoon_reader.view.*
|
||||||
|
import rx.Subscription
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.subjects.PublishSubject
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder for webtoon reader for a single page of a chapter.
|
||||||
|
* All the elements from the layout file "item_webtoon_reader" are available in this class.
|
||||||
|
*
|
||||||
|
* @param view the inflated view for this holder.
|
||||||
|
* @param adapter the adapter handling this holder.
|
||||||
|
* @constructor creates a new webtoon holder.
|
||||||
|
*/
|
||||||
|
class WebtoonHolder(private val view: View, private val adapter: WebtoonAdapter) :
|
||||||
|
RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page of a chapter.
|
||||||
|
*/
|
||||||
|
private var page: Page? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription for status changes of the page.
|
||||||
|
*/
|
||||||
|
private var statusSubscription: Subscription? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout of decode error.
|
||||||
|
*/
|
||||||
|
private var decodeErrorLayout: PageDecodeErrorLayout? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
with(view.image_view) {
|
||||||
|
setParallelLoadingEnabled(true)
|
||||||
|
setMaxBitmapDimensions(readerActivity.maxBitmapSize)
|
||||||
|
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED)
|
||||||
|
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
|
||||||
|
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH)
|
||||||
|
maxScale = 10f
|
||||||
|
setRegionDecoderClass(webtoonReader.regionDecoderClass)
|
||||||
|
setBitmapDecoderClass(webtoonReader.bitmapDecoderClass)
|
||||||
|
setVerticalScrollingParent(true)
|
||||||
|
setOnTouchListener(adapter.touchListener)
|
||||||
|
setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
||||||
|
override fun onImageLoaded() {
|
||||||
|
// When the image is loaded, reset the minimum height to avoid gaps
|
||||||
|
view.frame_container.minimumHeight = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onImageLoadError(e: Exception) {
|
||||||
|
onImageDecodeError()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid to create a lot of view holders taking twice the screen height,
|
||||||
|
// saving memory and a possible OOM. When the first image is loaded in this holder,
|
||||||
|
// the minimum size will be removed.
|
||||||
|
// Doing this we get sequential holder instantiation.
|
||||||
|
view.frame_container.minimumHeight = view.resources.displayMetrics.heightPixels * 2
|
||||||
|
|
||||||
|
// Leave some space between progress bars
|
||||||
|
view.progress.minimumHeight = 300
|
||||||
|
|
||||||
|
view.frame_container.setOnTouchListener(adapter.touchListener)
|
||||||
|
view.retry_button.setOnTouchListener { v, event ->
|
||||||
|
if (event.action == MotionEvent.ACTION_UP) {
|
||||||
|
readerActivity.presenter.retryPage(page)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called from [WebtoonAdapter.onBindViewHolder]. It updates the data for this
|
||||||
|
* holder with the given page.
|
||||||
|
*
|
||||||
|
* @param page the page to bind.
|
||||||
|
*/
|
||||||
|
fun onSetValues(page: Page) {
|
||||||
|
decodeErrorLayout?.let {
|
||||||
|
(view as ViewGroup).removeView(it)
|
||||||
|
decodeErrorLayout = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.page = page
|
||||||
|
observeStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the status of the page and notify the changes.
|
||||||
|
*
|
||||||
|
* @see processStatus
|
||||||
|
*/
|
||||||
|
private fun observeStatus() {
|
||||||
|
page?.let { page ->
|
||||||
|
val statusSubject = PublishSubject.create<Int>()
|
||||||
|
page.setStatusSubject(statusSubject)
|
||||||
|
|
||||||
|
statusSubscription?.unsubscribe()
|
||||||
|
statusSubscription = statusSubject.startWith(page.status)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { processStatus(it) }
|
||||||
|
|
||||||
|
webtoonReader.subscriptions.add(statusSubscription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the status of the page changes.
|
||||||
|
*
|
||||||
|
* @param status the new status of the page.
|
||||||
|
*/
|
||||||
|
private fun processStatus(status: Int) {
|
||||||
|
when (status) {
|
||||||
|
Page.QUEUE -> onQueue()
|
||||||
|
Page.LOAD_PAGE -> onLoading()
|
||||||
|
Page.DOWNLOAD_IMAGE -> onLoading()
|
||||||
|
Page.READY -> onReady()
|
||||||
|
Page.ERROR -> onError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes from the status subscription.
|
||||||
|
*/
|
||||||
|
fun unsubscribeStatus() {
|
||||||
|
statusSubscription?.unsubscribe()
|
||||||
|
statusSubscription = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the page is loading.
|
||||||
|
*/
|
||||||
|
private fun onLoading() {
|
||||||
|
setRetryButtonVisible(false)
|
||||||
|
setImageVisible(false)
|
||||||
|
setProgressVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the page is ready.
|
||||||
|
*/
|
||||||
|
private fun onReady() {
|
||||||
|
setRetryButtonVisible(false)
|
||||||
|
setProgressVisible(false)
|
||||||
|
setImageVisible(true)
|
||||||
|
|
||||||
|
page?.imagePath?.let { path ->
|
||||||
|
if (File(path).exists()) {
|
||||||
|
view.image_view.setImage(ImageSource.uri(path))
|
||||||
|
view.progress.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
page?.status = Page.ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the page has an error.
|
||||||
|
*/
|
||||||
|
private fun onError() {
|
||||||
|
setImageVisible(false)
|
||||||
|
setProgressVisible(false)
|
||||||
|
setRetryButtonVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the page is queued.
|
||||||
|
*/
|
||||||
|
private fun onQueue() {
|
||||||
|
setImageVisible(false)
|
||||||
|
setRetryButtonVisible(false)
|
||||||
|
setProgressVisible(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the image fails to decode.
|
||||||
|
*/
|
||||||
|
private fun onImageDecodeError() {
|
||||||
|
page?.let { page ->
|
||||||
|
decodeErrorLayout = PageDecodeErrorLayout(view.context, page, readerActivity.readerTheme,
|
||||||
|
{ readerActivity.presenter.retryPage(page) })
|
||||||
|
|
||||||
|
(view as ViewGroup).addView(decodeErrorLayout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the visibility of the progress bar.
|
||||||
|
*
|
||||||
|
* @param visible whether to show it or not.
|
||||||
|
*/
|
||||||
|
private fun setProgressVisible(visible: Boolean) {
|
||||||
|
view.progress.visibility = if (visible) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the visibility of the image view.
|
||||||
|
*
|
||||||
|
* @param visible whether to show it or not.
|
||||||
|
*/
|
||||||
|
private fun setImageVisible(visible: Boolean) {
|
||||||
|
view.image_view.visibility = if (visible) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the visibility of the retry button.
|
||||||
|
*
|
||||||
|
* @param visible whether to show it or not.
|
||||||
|
*/
|
||||||
|
private fun setRetryButtonVisible(visible: Boolean) {
|
||||||
|
view.retry_button.visibility = if (visible) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property to get the reader activity.
|
||||||
|
*/
|
||||||
|
private val readerActivity: ReaderActivity
|
||||||
|
get() = adapter.fragment.readerActivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property to get the webtoon reader.
|
||||||
|
*/
|
||||||
|
private val webtoonReader: WebtoonReader
|
||||||
|
get() = adapter.fragment
|
||||||
|
}
|
@ -1,204 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader;
|
|
||||||
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
|
||||||
import rx.subjects.PublishSubject;
|
|
||||||
|
|
||||||
import static android.view.GestureDetector.SimpleOnGestureListener;
|
|
||||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
|
||||||
|
|
||||||
public class WebtoonReader extends BaseReader {
|
|
||||||
|
|
||||||
private WebtoonAdapter adapter;
|
|
||||||
private RecyclerView recycler;
|
|
||||||
private PreCachingLayoutManager layoutManager;
|
|
||||||
private Subscription subscription;
|
|
||||||
private Subscription decoderSubscription;
|
|
||||||
protected GestureDetector gestureDetector;
|
|
||||||
|
|
||||||
private int scrollDistance;
|
|
||||||
|
|
||||||
private static final String SAVED_POSITION = "saved_position";
|
|
||||||
|
|
||||||
private static final float LEFT_REGION = 0.33f;
|
|
||||||
private static final float RIGHT_REGION = 0.66f;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
|
||||||
adapter = new WebtoonAdapter(this);
|
|
||||||
|
|
||||||
int screenHeight = getResources().getDisplayMetrics().heightPixels;
|
|
||||||
scrollDistance = screenHeight * 3 / 4;
|
|
||||||
|
|
||||||
layoutManager = new PreCachingLayoutManager(getActivity());
|
|
||||||
layoutManager.setExtraLayoutSpace(screenHeight / 2);
|
|
||||||
if (savedState != null) {
|
|
||||||
layoutManager.scrollToPositionWithOffset(savedState.getInt(SAVED_POSITION), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
recycler = new RecyclerView(getActivity());
|
|
||||||
recycler.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
|
||||||
recycler.setLayoutManager(layoutManager);
|
|
||||||
recycler.setItemAnimator(null);
|
|
||||||
recycler.setAdapter(adapter);
|
|
||||||
|
|
||||||
decoderSubscription = getReaderActivity().getPreferences().imageDecoder()
|
|
||||||
.asObservable()
|
|
||||||
.doOnNext(this::setDecoderClass)
|
|
||||||
.skip(1)
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.subscribe(v -> recycler.setAdapter(adapter));
|
|
||||||
|
|
||||||
gestureDetector = new GestureDetector(recycler.getContext(), new SimpleOnGestureListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
|
||||||
final float positionX = e.getX();
|
|
||||||
|
|
||||||
if (positionX < recycler.getWidth() * LEFT_REGION) {
|
|
||||||
moveToPrevious();
|
|
||||||
} else if (positionX > recycler.getWidth() * RIGHT_REGION) {
|
|
||||||
moveToNext();
|
|
||||||
} else {
|
|
||||||
getReaderActivity().onCenterSingleTap();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setPages();
|
|
||||||
return recycler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
decoderSubscription.unsubscribe();
|
|
||||||
super.onDestroyView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
unsubscribeStatus();
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
int savedPosition = pages != null ?
|
|
||||||
pages.get(layoutManager.findFirstVisibleItemPosition()).getPageNumber() : 0;
|
|
||||||
outState.putInt(SAVED_POSITION, savedPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unsubscribeStatus() {
|
|
||||||
if (subscription != null && !subscription.isUnsubscribed())
|
|
||||||
subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSelectedPage(int pageNumber) {
|
|
||||||
recycler.scrollToPosition(pageNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void moveToNext() {
|
|
||||||
recycler.smoothScrollBy(0, scrollDistance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void moveToPrevious() {
|
|
||||||
recycler.smoothScrollBy(0, -scrollDistance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSetChapter(Chapter chapter, Page currentPage) {
|
|
||||||
pages = new ArrayList<>(chapter.getPages());
|
|
||||||
// Restoring current page is not supported. It's getting weird scrolling jumps
|
|
||||||
// this.currentPage = currentPage;
|
|
||||||
|
|
||||||
// This method can be called before the view is created
|
|
||||||
if (recycler != null) {
|
|
||||||
setPages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAppendChapter(Chapter chapter) {
|
|
||||||
int insertStart = pages.size();
|
|
||||||
pages.addAll(chapter.getPages());
|
|
||||||
|
|
||||||
// This method can be called before the view is created
|
|
||||||
if (recycler != null) {
|
|
||||||
adapter.setPages(pages);
|
|
||||||
adapter.notifyItemRangeInserted(insertStart, chapter.getPages().size());
|
|
||||||
if (subscription != null && subscription.isUnsubscribed()) {
|
|
||||||
observeStatus(insertStart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPages() {
|
|
||||||
if (pages != null) {
|
|
||||||
unsubscribeStatus();
|
|
||||||
recycler.clearOnScrollListeners();
|
|
||||||
adapter.setPages(pages);
|
|
||||||
recycler.setAdapter(adapter);
|
|
||||||
updatePageNumber();
|
|
||||||
setScrollListener();
|
|
||||||
observeStatus(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setScrollListener() {
|
|
||||||
recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
|
||||||
@Override
|
|
||||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
|
||||||
int page = layoutManager.findLastVisibleItemPosition();
|
|
||||||
if (page != currentPage) {
|
|
||||||
onPageChanged(page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void observeStatus(int position) {
|
|
||||||
if (position == pages.size()) {
|
|
||||||
unsubscribeStatus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Page page = pages.get(position);
|
|
||||||
|
|
||||||
PublishSubject<Integer> statusSubject = PublishSubject.create();
|
|
||||||
page.setStatusSubject(statusSubject);
|
|
||||||
|
|
||||||
// Unsubscribe from the previous page
|
|
||||||
unsubscribeStatus();
|
|
||||||
|
|
||||||
subscription = statusSubject
|
|
||||||
.startWith(page.getStatus())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(status -> processStatus(position, status));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processStatus(int position, int status) {
|
|
||||||
adapter.notifyItemChanged(position);
|
|
||||||
if (status == Page.READY) {
|
|
||||||
observeStatus(position + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,203 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.view.*
|
||||||
|
import android.view.GestureDetector.SimpleOnGestureListener
|
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
|
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
|
||||||
|
import rx.subscriptions.CompositeSubscription
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a reader for webtoons based on a RecyclerView.
|
||||||
|
*/
|
||||||
|
class WebtoonReader : BaseReader() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Key to save and restore the position of the layout manager.
|
||||||
|
*/
|
||||||
|
private val SAVED_POSITION = "saved_position"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Left side region of the screen. Used for touch events.
|
||||||
|
*/
|
||||||
|
private val LEFT_REGION = 0.33f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Right side region of the screen. Used for touch events.
|
||||||
|
*/
|
||||||
|
private val RIGHT_REGION = 0.66f
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecyclerView of the reader.
|
||||||
|
*/
|
||||||
|
lateinit var recycler: RecyclerView
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter of the recycler.
|
||||||
|
*/
|
||||||
|
lateinit var adapter: WebtoonAdapter
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout manager of the recycler.
|
||||||
|
*/
|
||||||
|
lateinit var layoutManager: PreCachingLayoutManager
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gesture detector for touch events.
|
||||||
|
*/
|
||||||
|
val gestureDetector by lazy { createGestureDetector() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscriptions used while the view exists.
|
||||||
|
*/
|
||||||
|
lateinit var subscriptions: CompositeSubscription
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var scrollDistance: Int = 0
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
|
adapter = WebtoonAdapter(this)
|
||||||
|
|
||||||
|
val screenHeight = resources.displayMetrics.heightPixels
|
||||||
|
scrollDistance = screenHeight * 3 / 4
|
||||||
|
|
||||||
|
layoutManager = PreCachingLayoutManager(activity)
|
||||||
|
layoutManager.setExtraLayoutSpace(screenHeight / 2)
|
||||||
|
if (savedState != null) {
|
||||||
|
layoutManager.scrollToPositionWithOffset(savedState.getInt(SAVED_POSITION), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
recycler = RecyclerView(activity).apply {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
||||||
|
itemAnimator = null
|
||||||
|
}
|
||||||
|
recycler.layoutManager = layoutManager
|
||||||
|
recycler.adapter = adapter
|
||||||
|
recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
|
||||||
|
val page = layoutManager.findLastVisibleItemPosition()
|
||||||
|
if (page != currentPage) {
|
||||||
|
onPageChanged(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
subscriptions = CompositeSubscription()
|
||||||
|
subscriptions.add(readerActivity.preferences.imageDecoder()
|
||||||
|
.asObservable()
|
||||||
|
.doOnNext { setDecoderClass(it) }
|
||||||
|
.skip(1)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.subscribe { recycler.adapter = adapter })
|
||||||
|
|
||||||
|
setPagesOnAdapter()
|
||||||
|
return recycler
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
subscriptions.unsubscribe()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
val savedPosition = pages[layoutManager.findFirstVisibleItemPosition()].pageNumber
|
||||||
|
outState.putInt(SAVED_POSITION, savedPosition)
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the gesture detector for the reader.
|
||||||
|
*
|
||||||
|
* @return a gesture detector.
|
||||||
|
*/
|
||||||
|
protected fun createGestureDetector(): GestureDetector {
|
||||||
|
return GestureDetector(context, object : SimpleOnGestureListener() {
|
||||||
|
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
||||||
|
val positionX = e.x
|
||||||
|
|
||||||
|
if (positionX < recycler.width * LEFT_REGION) {
|
||||||
|
moveToPrevious()
|
||||||
|
} else if (positionX > recycler.width * RIGHT_REGION) {
|
||||||
|
moveToNext()
|
||||||
|
} else {
|
||||||
|
readerActivity.onCenterSingleTap()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a new chapter is set in [BaseReader].
|
||||||
|
*
|
||||||
|
* @param chapter the chapter set.
|
||||||
|
* @param currentPage the initial page to display.
|
||||||
|
*/
|
||||||
|
override fun onChapterSet(chapter: Chapter, currentPage: Page) {
|
||||||
|
// Restoring current page is not supported. It's getting weird scrolling jumps
|
||||||
|
// this.currentPage = currentPage;
|
||||||
|
|
||||||
|
// Make sure the view is already initialized.
|
||||||
|
if (view != null) {
|
||||||
|
setPagesOnAdapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a chapter is appended in [BaseReader].
|
||||||
|
*
|
||||||
|
* @param chapter the chapter appended.
|
||||||
|
*/
|
||||||
|
override fun onChapterAppended(chapter: Chapter) {
|
||||||
|
// Make sure the view is already initialized.
|
||||||
|
if (view != null) {
|
||||||
|
val insertStart = pages.size - chapter.pages.size
|
||||||
|
adapter.notifyItemRangeInserted(insertStart, chapter.pages.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the pages on the adapter.
|
||||||
|
*/
|
||||||
|
private fun setPagesOnAdapter() {
|
||||||
|
if (pages.isNotEmpty()) {
|
||||||
|
adapter.pages = pages
|
||||||
|
recycler.adapter = adapter
|
||||||
|
updatePageNumber()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the active page.
|
||||||
|
*
|
||||||
|
* @param pageNumber the index of the page from [pages].
|
||||||
|
*/
|
||||||
|
override fun setActivePage(pageNumber: Int) {
|
||||||
|
recycler.scrollToPosition(pageNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves to the next page or requests the next chapter if it's the last one.
|
||||||
|
*/
|
||||||
|
override fun moveToNext() {
|
||||||
|
recycler.smoothScrollBy(0, scrollDistance)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves to the previous page or requests the previous chapter if it's the first one.
|
||||||
|
*/
|
||||||
|
override fun moveToPrevious() {
|
||||||
|
recycler.smoothScrollBy(0, -scrollDistance)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,4 +3,4 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/page_image_view" />
|
android:id="@+id/image_view" />
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
Loading…
Reference in New Issue
Block a user