From ff61282104230dda341dc7555fa5a892df9dd910 Mon Sep 17 00:00:00 2001 From: len Date: Fri, 4 Mar 2016 14:10:41 +0100 Subject: [PATCH] Readers in Kotlin. Also fix #193 --- .../tachiyomi/ui/reader/ReaderActivity.java | 13 +- .../tachiyomi/ui/reader/ReaderMenu.java | 15 +- .../ui/reader/viewer/base/BaseReader.java | 130 -------- .../ui/reader/viewer/base/BaseReader.kt | 222 +++++++++++++ .../viewer/base/PageDecodeErrorLayout.java | 67 ---- .../viewer/base/PageDecodeErrorLayout.kt | 65 ++++ .../pager/OnChapterBoundariesOutListener.java | 6 - .../pager/OnChapterBoundariesOutListener.kt | 6 + .../ui/reader/viewer/pager/PagerReader.java | 196 ------------ .../ui/reader/viewer/pager/PagerReader.kt | 287 +++++++++++++++++ .../viewer/pager/PagerReaderAdapter.java | 60 ---- .../reader/viewer/pager/PagerReaderAdapter.kt | 78 +++++ .../viewer/pager/PagerReaderFragment.java | 270 ---------------- .../viewer/pager/PagerReaderFragment.kt | 294 ++++++++++++++++++ .../pager/horizontal/HorizontalPager.java | 87 ------ .../pager/horizontal/HorizontalPager.kt | 86 +++++ .../pager/horizontal/LeftToRightReader.java | 19 -- .../pager/horizontal/LeftToRightReader.kt | 19 ++ .../pager/horizontal/RightToLeftReader.java | 30 -- .../pager/horizontal/RightToLeftReader.kt | 30 ++ .../viewer/pager/vertical/VerticalPager.java | 86 ----- .../viewer/pager/vertical/VerticalPager.kt | 84 +++++ .../viewer/pager/vertical/VerticalReader.java | 19 -- .../viewer/pager/vertical/VerticalReader.kt | 19 ++ .../reader/viewer/webtoon/WebtoonAdapter.java | 72 ----- .../reader/viewer/webtoon/WebtoonAdapter.kt | 78 +++++ .../reader/viewer/webtoon/WebtoonHolder.java | 135 -------- .../ui/reader/viewer/webtoon/WebtoonHolder.kt | 240 ++++++++++++++ .../reader/viewer/webtoon/WebtoonReader.java | 204 ------------ .../ui/reader/viewer/webtoon/WebtoonReader.kt | 203 ++++++++++++ app/src/main/res/layout/chapter_image.xml | 2 +- .../main/res/layout/item_webtoon_reader.xml | 2 +- 32 files changed, 1730 insertions(+), 1394 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java index 7c217e8f8..cec18311e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java @@ -97,13 +97,14 @@ public class ReaderActivity extends BaseRxActivity { @Override protected void onPause() { if (viewer != null) - getPresenter().setCurrentPage(viewer.getCurrentPage()); + getPresenter().setCurrentPage(viewer.getActivePage()); super.onPause(); } @Override protected void onDestroy() { subscriptions.unsubscribe(); + readerMenu.destroy(); viewer = null; super.onDestroy(); } @@ -127,7 +128,7 @@ public class ReaderActivity extends BaseRxActivity { @Override public void onBackPressed() { if (viewer != null) - getPresenter().setCurrentPage(viewer.getCurrentPage()); + getPresenter().setCurrentPage(viewer.getActivePage()); getPresenter().onChapterLeft(); int chapterToUpdate = getPresenter().getMangaSyncChapterToUpdate(); @@ -255,8 +256,8 @@ public class ReaderActivity extends BaseRxActivity { } public void gotoPageInCurrentChapter(int pageIndex) { - Page requestedPage = viewer.getCurrentPage().getChapter().getPages().get(pageIndex); - viewer.setSelectedPage(requestedPage); + Page requestedPage = viewer.getActivePage().getChapter().getPages().get(pageIndex); + viewer.setActivePage(requestedPage); } public void onCenterSingleTap() { @@ -264,7 +265,7 @@ public class ReaderActivity extends BaseRxActivity { } public void requestNextChapter() { - getPresenter().setCurrentPage(viewer.getCurrentPage()); + getPresenter().setCurrentPage(viewer.getActivePage()); if (!getPresenter().loadNextChapter()) { ToastUtil.showShort(this, R.string.no_next_chapter); } @@ -272,7 +273,7 @@ public class ReaderActivity extends BaseRxActivity { } public void requestPreviousChapter() { - getPresenter().setCurrentPage(viewer.getCurrentPage()); + getPresenter().setCurrentPage(viewer.getActivePage()); if (!getPresenter().loadPreviousChapter()) { ToastUtil.showShort(this, R.string.no_previous_chapter); } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderMenu.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderMenu.java index 424b908dd..2674ba3d1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderMenu.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderMenu.java @@ -31,7 +31,6 @@ import eu.kanade.tachiyomi.R; import eu.kanade.tachiyomi.data.database.models.Chapter; import eu.kanade.tachiyomi.data.database.models.Manga; import eu.kanade.tachiyomi.data.preference.PreferencesHelper; -import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader; import icepick.State; import rx.Subscription; @@ -116,6 +115,12 @@ public class ReaderMenu { showing = false; } + public void destroy() { + if (settingsPopup != null) { + settingsPopup.dismiss(); + } + } + public boolean onCreateOptionsMenu(Menu menu) { activity.getMenuInflater().inflate(R.menu.reader, menu); nextChapterBtn = menu.findItem(R.id.action_next_chapter); @@ -349,12 +354,12 @@ public class ReaderMenu { private void setDecoderInitial(int decoder) { String initial; switch (decoder) { - case BaseReader.SKIA_DECODER: - initial = "S"; - break; - case BaseReader.RAPID_DECODER: + case 0: initial = "R"; break; + case 1: + initial = "S"; + break; default: initial = ""; break; diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.java deleted file mode 100644 index c5ceb6e97..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.java +++ /dev/null @@ -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 pages; - protected List chapters; - protected Class regionDecoderClass; - protected Class 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 getRegionDecoderClass() { - return regionDecoderClass; - } - - public Class getBitmapDecoderClass() { - return bitmapDecoderClass; - } - - public ReaderActivity getReaderActivity() { - return (ReaderActivity) getActivity(); - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt new file mode 100644 index 000000000..7ee01c073 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt @@ -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() + + /** + * List of pages added in the reader. It can contain pages from more than one chapter. + */ + var pages: MutableList = ArrayList() + private set + + /** + * Current visible position of [pages]. + */ + var currentPage: Int = 0 + protected set + + /** + * Region decoder class to use. + */ + lateinit var regionDecoderClass: Class + private set + + /** + * Bitmap decoder class to use. + */ + lateinit var bitmapDecoderClass: Class + 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() + 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 + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.java deleted file mode 100644 index 6469d9c86..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt new file mode 100644 index 000000000..351f3beab --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt @@ -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) + } + + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.java deleted file mode 100644 index 4de17802a..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package eu.kanade.tachiyomi.ui.reader.viewer.pager; - -public interface OnChapterBoundariesOutListener { - void onFirstPageOutEvent(); - void onLastPageOutEvent(); -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.kt new file mode 100644 index 000000000..150711349 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.kt @@ -0,0 +1,6 @@ +package eu.kanade.tachiyomi.ui.reader.viewer.pager + +interface OnChapterBoundariesOutListener { + fun onFirstPageOutEvent() + fun onLastPageOutEvent() +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.java deleted file mode 100644 index 7e953948e..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.java +++ /dev/null @@ -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; - } - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt new file mode 100644 index 000000000..a0f7abfcb --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt @@ -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 + } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.java deleted file mode 100644 index bf84a25a9..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.java +++ /dev/null @@ -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 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 getPages() { - return pages; - } - - public void setPages(List 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); - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt new file mode 100644 index 000000000..9c2990b51 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt @@ -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? = 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) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.java deleted file mode 100644 index a4872f7d2..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.java +++ /dev/null @@ -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 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(); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt new file mode 100644 index 000000000..f7c91069f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt @@ -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() + 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 + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.java deleted file mode 100644 index 4d68990c2..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.java +++ /dev/null @@ -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 function) { - addOnPageChangeListener(new SimpleOnPageChangeListener() { - @Override - public void onPageSelected(int position) { - function.call(position); - } - }); - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt new file mode 100644 index 000000000..e12f773a7 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt @@ -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) { + addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { + override fun onPageSelected(position: Int) { + func.call(position) + } + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.java deleted file mode 100644 index faa7d5c80..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.java +++ /dev/null @@ -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; - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt new file mode 100644 index 000000000..39f533c3f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt @@ -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) } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.java deleted file mode 100644 index 2f74201bd..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.java +++ /dev/null @@ -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(); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt new file mode 100644 index 000000000..7a0eeb2d6 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt @@ -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() + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.java deleted file mode 100644 index 52892ac66..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.java +++ /dev/null @@ -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 function) { - addOnPageChangeListener(new SimpleOnPageChangeListener() { - @Override - public void onPageSelected(int position) { - function.call(position); - } - }); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.kt new file mode 100644 index 000000000..b39ffd9aa --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.kt @@ -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) { + addOnPageChangeListener(object : VerticalViewPagerImpl.SimpleOnPageChangeListener() { + override fun onPageSelected(position: Int) { + func.call(position) + } + }) + } + + companion object { + + private val SWIPE_TOLERANCE = 0.25f + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.java deleted file mode 100644 index 635032209..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.java +++ /dev/null @@ -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; - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt new file mode 100644 index 000000000..3a5716025 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt @@ -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) } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.java deleted file mode 100644 index 4bab8228d..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.java +++ /dev/null @@ -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 { - - private WebtoonReader fragment; - private List 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 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(); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt new file mode 100644 index 000000000..6fc823343 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt @@ -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() { + + /** + * Pages stored in the adapter. + */ + var pages: List? = 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() + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.java deleted file mode 100644 index 9e5d8ee60..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt new file mode 100644 index 000000000..1ab3aed22 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt @@ -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() + 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 +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.java deleted file mode 100644 index 7cda3d155..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.java +++ /dev/null @@ -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 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); - } - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt new file mode 100644 index 000000000..5438d9c3c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt @@ -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) + } + +} diff --git a/app/src/main/res/layout/chapter_image.xml b/app/src/main/res/layout/chapter_image.xml index 7ae798b1a..29f084acb 100644 --- a/app/src/main/res/layout/chapter_image.xml +++ b/app/src/main/res/layout/chapter_image.xml @@ -3,4 +3,4 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@+id/page_image_view" /> + android:id="@+id/image_view" /> diff --git a/app/src/main/res/layout/item_webtoon_reader.xml b/app/src/main/res/layout/item_webtoon_reader.xml index 37d553ba7..84274e9e5 100644 --- a/app/src/main/res/layout/item_webtoon_reader.xml +++ b/app/src/main/res/layout/item_webtoon_reader.xml @@ -2,7 +2,7 @@ + android:layout_height="wrap_content">