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