0) || (y > getHeight() - mGutterSize && dy < 0);
- }
-
- private void enableLayers(boolean enable) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final int layerType = enable ?
- ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
- ViewCompat.setLayerType(getChildAt(i), layerType, null);
- }
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- /*
- * This method JUST determines whether we want to intercept the motion.
- * If we return true, onMotionEvent will be called and we do the actual
- * scrolling there.
- */
-
- final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
-
- // Always take care of the touch gesture being complete.
- if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
- // Release the drag.
- if (DEBUG) Log.v(TAG, "Intercept done!");
- resetTouch();
- return false;
- }
-
- // Nothing more to do here if we have decided whether or not we
- // are dragging.
- if (action != MotionEvent.ACTION_DOWN) {
- if (mIsBeingDragged) {
- if (DEBUG) Log.v(TAG, "Intercept returning true!");
- return true;
- }
- if (mIsUnableToDrag) {
- if (DEBUG) Log.v(TAG, "Intercept returning false!");
- return false;
- }
- }
-
- switch (action) {
- case MotionEvent.ACTION_MOVE: {
- /*
- * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
- * whether the user has moved far enough from his original down touch.
- */
-
- /*
- * Locally do absolute value. mLastMotionY is set to the y value
- * of the down event.
- */
- final int activePointerId = mActivePointerId;
- if (activePointerId == INVALID_POINTER) {
- // If we don't have a valid id, the touch down wasn't on content.
- break;
- }
-
- final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
- final float y = MotionEventCompat.getY(ev, pointerIndex);
- final float dy = y - mLastMotionY;
- final float yDiff = Math.abs(dy);
- final float x = MotionEventCompat.getX(ev, pointerIndex);
- final float xDiff = Math.abs(x - mInitialMotionX);
- if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
-
- if (dy != 0 && !isGutterDrag(mLastMotionY, dy) &&
- canScroll(this, false, (int) dy, (int) x, (int) y)) {
- // Nested view has scrollable area under this point. Let it be handled there.
- mLastMotionX = x;
- mLastMotionY = y;
- mIsUnableToDrag = true;
- return false;
- }
- if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {
- if (DEBUG) Log.v(TAG, "Starting drag!");
- mIsBeingDragged = true;
- requestParentDisallowInterceptTouchEvent(true);
- setScrollState(SCROLL_STATE_DRAGGING);
- mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :
- mInitialMotionY - mTouchSlop;
- mLastMotionX = x;
- setScrollingCacheEnabled(true);
- } else if (xDiff > mTouchSlop) {
- // The finger has moved enough in the vertical
- // direction to be counted as a drag... abort
- // any attempt to drag horizontally, to work correctly
- // with children that have scrolling containers.
- if (DEBUG) Log.v(TAG, "Starting unable to drag!");
- mIsUnableToDrag = true;
- }
- // Scroll to follow the motion event
- if (mIsBeingDragged && performDrag(y)) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- break;
- }
-
- case MotionEvent.ACTION_DOWN: {
- /*
- * Remember location of down touch.
- * ACTION_DOWN always refers to pointer index 0.
- */
- mLastMotionX = mInitialMotionX = ev.getX();
- mLastMotionY = mInitialMotionY = ev.getY();
- mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
- mIsUnableToDrag = false;
-
- mScroller.computeScrollOffset();
- if (mScrollState == SCROLL_STATE_SETTLING &&
- Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {
- // Let the user 'catch' the pager as it animates.
- mScroller.abortAnimation();
- mPopulatePending = false;
- populate();
- mIsBeingDragged = true;
- requestParentDisallowInterceptTouchEvent(true);
- setScrollState(SCROLL_STATE_DRAGGING);
- } else {
- completeScroll(false);
- mIsBeingDragged = false;
- }
-
- if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
- + " mIsBeingDragged=" + mIsBeingDragged
- + "mIsUnableToDrag=" + mIsUnableToDrag);
- break;
- }
-
- case MotionEventCompat.ACTION_POINTER_UP:
- onSecondaryPointerUp(ev);
- break;
- default:
- break;
- }
-
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(ev);
-
- /*
- * The only time we want to intercept motion events is if we are in the
- * drag mode.
- */
- return mIsBeingDragged;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (mFakeDragging) {
- // A fake drag is in progress already, ignore this real one
- // but still eat the touch events.
- // (It is likely that the user is multi-touching the screen.)
- return true;
- }
-
- if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
- // Don't handle edge touches immediately -- they may actually belong to one of our
- // descendants.
- return false;
- }
-
- if (mAdapter == null || mAdapter.getCount() == 0) {
- // Nothing to present or scroll; nothing to touch.
- return false;
- }
-
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(ev);
-
- final int action = ev.getAction();
- boolean needsInvalidate = false;
-
- switch (action & MotionEventCompat.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- mScroller.abortAnimation();
- mPopulatePending = false;
- populate();
-
- // Remember where the motion event started
- mLastMotionX = mInitialMotionX = ev.getX();
- mLastMotionY = mInitialMotionY = ev.getY();
- mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
- break;
- }
- case MotionEvent.ACTION_MOVE:
- if (!mIsBeingDragged) {
- final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
- if (pointerIndex == -1) {
- // A child has consumed some touch events and put us into an inconsistent state.
- needsInvalidate = resetTouch();
- break;
- }
- final float y = MotionEventCompat.getY(ev, pointerIndex);
- final float yDiff = Math.abs(y - mLastMotionY);
- final float x = MotionEventCompat.getX(ev, pointerIndex);
- final float xDiff = Math.abs(x - mLastMotionX);
- if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
- if (yDiff > mTouchSlop && yDiff > xDiff) {
- if (DEBUG) Log.v(TAG, "Starting drag!");
- mIsBeingDragged = true;
- requestParentDisallowInterceptTouchEvent(true);
- mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :
- mInitialMotionY - mTouchSlop;
- mLastMotionX = x;
- setScrollState(SCROLL_STATE_DRAGGING);
- setScrollingCacheEnabled(true);
-
- // Disallow Parent Intercept, just in case
- ViewParent parent = getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
- }
- }
- // Not else! Note that mIsBeingDragged can be set above.
- if (mIsBeingDragged) {
- // Scroll to follow the motion event
- final int activePointerIndex = MotionEventCompat.findPointerIndex(
- ev, mActivePointerId);
- final float y = MotionEventCompat.getY(ev, activePointerIndex);
- needsInvalidate |= performDrag(y);
- }
- break;
- case MotionEvent.ACTION_UP:
- if (mIsBeingDragged) {
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
- velocityTracker, mActivePointerId);
- mPopulatePending = true;
- final int height = getClientHeight();
- final int scrollY = getScrollY();
- final ItemInfo ii = infoForCurrentScrollPosition();
- final int currentPage = ii.position;
- final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
- final int activePointerIndex =
- MotionEventCompat.findPointerIndex(ev, mActivePointerId);
- final float y = MotionEventCompat.getY(ev, activePointerIndex);
- final int totalDelta = (int) (y - mInitialMotionY);
- int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
- totalDelta);
- setCurrentItemInternal(nextPage, true, true, initialVelocity);
-
- needsInvalidate = resetTouch();
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- if (mIsBeingDragged) {
- scrollToItem(mCurItem, true, 0, false);
- needsInvalidate = resetTouch();
- }
- break;
- case MotionEventCompat.ACTION_POINTER_DOWN: {
- final int index = MotionEventCompat.getActionIndex(ev);
- final float y = MotionEventCompat.getY(ev, index);
- mLastMotionY = y;
- mActivePointerId = MotionEventCompat.getPointerId(ev, index);
- break;
- }
- case MotionEventCompat.ACTION_POINTER_UP:
- onSecondaryPointerUp(ev);
- mLastMotionY = MotionEventCompat.getY(ev,
- MotionEventCompat.findPointerIndex(ev, mActivePointerId));
- break;
- default:
- break;
- }
- if (needsInvalidate) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- return true;
- }
-
- private boolean resetTouch() {
- boolean needsInvalidate;
- mActivePointerId = INVALID_POINTER;
- endDrag();
- needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
- return needsInvalidate;
- }
-
- private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
- final ViewParent parent = getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(disallowIntercept);
- }
- }
-
- private boolean performDrag(float y) {
- boolean needsInvalidate = false;
-
- final float deltaY = mLastMotionY - y;
- mLastMotionY = y;
-
- float oldScrollY = getScrollY();
- float scrollY = oldScrollY + deltaY;
- final int height = getClientHeight();
-
- float topBound = height * mFirstOffset;
- float bottomBound = height * mLastOffset;
- boolean topAbsolute = true;
- boolean bottomAbsolute = true;
-
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
- if (firstItem.position != 0) {
- topAbsolute = false;
- topBound = firstItem.offset * height;
- }
- if (lastItem.position != mAdapter.getCount() - 1) {
- bottomAbsolute = false;
- bottomBound = lastItem.offset * height;
- }
-
- if (scrollY < topBound) {
- if (topAbsolute) {
- float over = topBound - scrollY;
- needsInvalidate = mTopEdge.onPull(Math.abs(over) / height);
- }
- scrollY = topBound;
- } else if (scrollY > bottomBound) {
- if (bottomAbsolute) {
- float over = scrollY - bottomBound;
- needsInvalidate = mBottomEdge.onPull(Math.abs(over) / height);
- }
- scrollY = bottomBound;
- }
- // Don't lose the rounded component
- mLastMotionY += scrollY - (int) scrollY;
- scrollTo(getScrollX(), (int) scrollY);
- pageScrolled((int) scrollY);
-
- return needsInvalidate;
- }
-
- /**
- * @return Info about the page at the current scroll position.
- * This can be synthetic for a missing middle page; the 'object' field can be null.
- */
- private ItemInfo infoForCurrentScrollPosition() {
- final int height = getClientHeight();
- final float scrollOffset = height > 0 ? (float) getScrollY() / height : 0;
- final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
- int lastPos = -1;
- float lastOffset = 0.f;
- float lastHeight = 0.f;
- boolean first = true;
-
- ItemInfo lastItem = null;
- for (int i = 0; i < mItems.size(); i++) {
- ItemInfo ii = mItems.get(i);
- float offset;
- if (!first && ii.position != lastPos + 1) {
- // Create a synthetic item for a missing page.
- ii = mTempItem;
- ii.offset = lastOffset + lastHeight + marginOffset;
- ii.position = lastPos + 1;
- ii.heightFactor = mAdapter.getPageWidth(ii.position);
- i--;
- }
- offset = ii.offset;
-
- final float topBound = offset;
- final float bottomBound = offset + ii.heightFactor + marginOffset;
- if (first || scrollOffset >= topBound) {
- if (scrollOffset < bottomBound || i == mItems.size() - 1) {
- return ii;
- }
- } else {
- return lastItem;
- }
- first = false;
- lastPos = ii.position;
- lastOffset = offset;
- lastHeight = ii.heightFactor;
- lastItem = ii;
- }
-
- return lastItem;
- }
-
- private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaY) {
- int targetPage;
- if (Math.abs(deltaY) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
- targetPage = velocity > 0 ? currentPage : currentPage + 1;
- } else {
- final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
- targetPage = (int) (currentPage + pageOffset + truncator);
- }
-
- if (!mItems.isEmpty()) {
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
-
- // Only let the user target pages we have items for
- targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
- }
-
- return targetPage;
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- boolean needsInvalidate = false;
-
- final int overScrollMode = ViewCompat.getOverScrollMode(this);
- if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
- (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
- mAdapter != null && mAdapter.getCount() > 1)) {
- if (!mTopEdge.isFinished()) {
- final int restoreCount = canvas.save();
- final int height = getHeight();
- final int width = getWidth() - getPaddingLeft() - getPaddingRight();
-
- canvas.translate(getPaddingLeft(), mFirstOffset * height);
- mTopEdge.setSize(width, height);
- needsInvalidate |= mTopEdge.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- if (!mBottomEdge.isFinished()) {
- final int restoreCount = canvas.save();
- final int height = getHeight();
- final int width = getWidth() - getPaddingLeft() - getPaddingRight();
-
- canvas.rotate(180);
- canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);
- mBottomEdge.setSize(width, height);
- needsInvalidate |= mBottomEdge.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- } else {
- mTopEdge.finish();
- mBottomEdge.finish();
- }
-
- if (needsInvalidate) {
- // Keep animating
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- // Draw the margin drawable between pages if needed.
- if (mPageMargin > 0 && mMarginDrawable != null && !mItems.isEmpty() && mAdapter != null) {
- final int scrollY = getScrollY();
- final int height = getHeight();
-
- final float marginOffset = (float) mPageMargin / height;
- int itemIndex = 0;
- ItemInfo ii = mItems.get(0);
- float offset = ii.offset;
- final int itemCount = mItems.size();
- final int firstPos = ii.position;
- final int lastPos = mItems.get(itemCount - 1).position;
- for (int pos = firstPos; pos < lastPos; pos++) {
- while (pos > ii.position && itemIndex < itemCount) {
- ii = mItems.get(++itemIndex);
- }
-
- float drawAt;
- if (pos == ii.position) {
- drawAt = (ii.offset + ii.heightFactor) * height;
- offset = ii.offset + ii.heightFactor + marginOffset;
- } else {
- float heightFactor = mAdapter.getPageWidth(pos);
- drawAt = (offset + heightFactor) * height;
- offset += heightFactor + marginOffset;
- }
-
- if (drawAt + mPageMargin > scrollY) {
- mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt,
- mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));
- mMarginDrawable.draw(canvas);
- }
-
- if (drawAt > scrollY + height) {
- break; // No more visible, no sense in continuing
- }
- }
- }
- }
-
- /**
- * Start a fake drag of the pager.
- *
- * A fake drag can be useful if you want to synchronize the motion of the ViewPager
- * with the touch scrolling of another view, while still letting the ViewPager
- * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
- * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
- * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
- *
- *
During a fake drag the ViewPager will ignore all touch events. If a real drag
- * is already in progress, this method will return false.
- *
- * @return true if the fake drag began successfully, false if it could not be started.
- *
- * @see #fakeDragBy(float)
- * @see #endFakeDrag()
- */
- public boolean beginFakeDrag() {
- if (mIsBeingDragged) {
- return false;
- }
- mFakeDragging = true;
- setScrollState(SCROLL_STATE_DRAGGING);
- mInitialMotionY = mLastMotionY = 0;
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- } else {
- mVelocityTracker.clear();
- }
- final long time = SystemClock.uptimeMillis();
- final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
- mVelocityTracker.addMovement(ev);
- ev.recycle();
- mFakeDragBeginTime = time;
- return true;
- }
-
- /**
- * End a fake drag of the pager.
- *
- * @see #beginFakeDrag()
- * @see #fakeDragBy(float)
- */
- public void endFakeDrag() {
- if (!mFakeDragging) {
- throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
- }
-
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
- velocityTracker, mActivePointerId);
- mPopulatePending = true;
- final int height = getClientHeight();
- final int scrollY = getScrollY();
- final ItemInfo ii = infoForCurrentScrollPosition();
- final int currentPage = ii.position;
- final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
- final int totalDelta = (int) (mLastMotionY - mInitialMotionY);
- int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
- totalDelta);
- setCurrentItemInternal(nextPage, true, true, initialVelocity);
- endDrag();
-
- mFakeDragging = false;
- }
-
- /**
- * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
- *
- * @param yOffset Offset in pixels to drag by.
- * @see #beginFakeDrag()
- * @see #endFakeDrag()
- */
- public void fakeDragBy(float yOffset) {
- if (!mFakeDragging) {
- throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
- }
-
- mLastMotionY += yOffset;
-
- float oldScrollY = getScrollY();
- float scrollY = oldScrollY - yOffset;
- final int height = getClientHeight();
-
- float topBound = height * mFirstOffset;
- float bottomBound = height * mLastOffset;
-
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
- if (firstItem.position != 0) {
- topBound = firstItem.offset * height;
- }
- if (lastItem.position != mAdapter.getCount() - 1) {
- bottomBound = lastItem.offset * height;
- }
-
- if (scrollY < topBound) {
- scrollY = topBound;
- } else if (scrollY > bottomBound) {
- scrollY = bottomBound;
- }
- // Don't lose the rounded component
- mLastMotionY += scrollY - (int) scrollY;
- scrollTo(getScrollX(), (int) scrollY);
- pageScrolled((int) scrollY);
-
- // Synthesize an event for the VelocityTracker.
- final long time = SystemClock.uptimeMillis();
- final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
- 0, mLastMotionY, 0);
- mVelocityTracker.addMovement(ev);
- ev.recycle();
- }
-
- /**
- * Returns true if a fake drag is in progress.
- *
- * @return true if currently in a fake drag, false otherwise.
- *
- * @see #beginFakeDrag()
- * @see #fakeDragBy(float)
- * @see #endFakeDrag()
- */
- public boolean isFakeDragging() {
- return mFakeDragging;
- }
-
- private void onSecondaryPointerUp(MotionEvent ev) {
- final int pointerIndex = MotionEventCompat.getActionIndex(ev);
- final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
- if (pointerId == mActivePointerId) {
- // This was our active pointer going up. Choose a new
- // active pointer and adjust accordingly.
- final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
- mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
- if (mVelocityTracker != null) {
- mVelocityTracker.clear();
- }
- }
- }
-
- private void endDrag() {
- mIsBeingDragged = false;
- mIsUnableToDrag = false;
-
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- private void setScrollingCacheEnabled(boolean enabled) {
- if (mScrollingCacheEnabled != enabled) {
- mScrollingCacheEnabled = enabled;
- if (USE_CACHE) {
- final int size = getChildCount();
- for (int i = 0; i < size; ++i) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- child.setDrawingCacheEnabled(enabled);
- }
- }
- }
- }
- }
-
- public boolean internalCanScrollVertically(int direction) {
- if (mAdapter == null) {
- return false;
- }
-
- final int height = getClientHeight();
- final int scrollY = getScrollY();
- if (direction < 0) {
- return (scrollY > (int) (height * mFirstOffset));
- } else if (direction > 0) {
- return (scrollY < (int) (height * mLastOffset));
- } else {
- return false;
- }
- }
-
- /**
- * Tests scrollability within child views of v given a delta of dx.
- *
- * @param v View to test for horizontal scrollability
- * @param checkV Whether the view v passed should itself be checked for scrollability (true),
- * or just its children (false).
- * @param dy Delta scrolled in pixels
- * @param x X coordinate of the active touch point
- * @param y Y coordinate of the active touch point
- * @return true if child views of v can be scrolled by delta of dx.
- */
- protected boolean canScroll(View v, boolean checkV, int dy, int x, int y) {
- if (v instanceof ViewGroup) {
- final ViewGroup group = (ViewGroup) v;
- final int scrollX = v.getScrollX();
- final int scrollY = v.getScrollY();
- final int count = group.getChildCount();
- // Count backwards - let topmost views consume scroll distance first.
- for (int i = count - 1; i >= 0; i--) {
- // TODO: Add versioned support here for transformed views.
- // This will not work for transformed views in Honeycomb+
- final View child = group.getChildAt(i);
- if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
- x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
- canScroll(child, true, dy, x + scrollX - child.getLeft(),
- y + scrollY - child.getTop())) {
- return true;
- }
- }
- }
-
- return checkV && ViewCompat.canScrollVertically(v, -dy);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- // Let the focused view and/or our descendants get the key first
- return super.dispatchKeyEvent(event) || executeKeyEvent(event);
- }
-
- /**
- * You can call this function yourself to have the scroll view perform
- * scrolling from a key event, just as if the event had been dispatched to
- * it by the view hierarchy.
- *
- * @param event The key event to execute.
- * @return Return true if the event was handled, else false.
- */
- public boolean executeKeyEvent(KeyEvent event) {
- boolean handled = false;
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- handled = arrowScroll(FOCUS_LEFT);
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- handled = arrowScroll(FOCUS_RIGHT);
- break;
- case KeyEvent.KEYCODE_TAB:
- if (event.hasNoModifiers()) {
- handled = arrowScroll(FOCUS_FORWARD);
- } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
- handled = arrowScroll(FOCUS_BACKWARD);
- }
- break;
- default:
- break;
- }
- }
- return handled;
- }
-
- public boolean arrowScroll(int direction) {
- View currentFocused = findFocus();
- if (currentFocused == this) {
- currentFocused = null;
- } else if (currentFocused != null) {
- boolean isChild = false;
- for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
- parent = parent.getParent()) {
- if (parent == this) {
- isChild = true;
- break;
- }
- }
- if (!isChild) {
- // This would cause the focus search down below to fail in fun ways.
- final StringBuilder sb = new StringBuilder();
- sb.append(currentFocused.getClass().getSimpleName());
- for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
- parent = parent.getParent()) {
- sb.append(" => ").append(parent.getClass().getSimpleName());
- }
- Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
- "current focused view " + sb.toString());
- currentFocused = null;
- }
- }
-
- boolean handled = false;
-
- View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
- direction);
- if (nextFocused != null && nextFocused != currentFocused) {
- if (direction == View.FOCUS_UP) {
- // If there is nothing to the left, or this is causing us to
- // jump to the right, then what we really want to do is page left.
- final int nextTop = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;
- final int currTop = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;
- if (currentFocused != null && nextTop >= currTop) {
- handled = pageUp();
- } else {
- handled = nextFocused.requestFocus();
- }
- } else if (direction == View.FOCUS_DOWN) {
- // If there is nothing to the right, or this is causing us to
- // jump to the left, then what we really want to do is page right.
- final int nextDown = getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;
- final int currDown = getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;
- if (currentFocused != null && nextDown <= currDown) {
- handled = pageDown();
- } else {
- handled = nextFocused.requestFocus();
- }
- }
- } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {
- // Trying to move left and nothing there; try to page.
- handled = pageUp();
- } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {
- // Trying to move right and nothing there; try to page.
- handled = pageDown();
- }
- if (handled) {
- playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
- }
- return handled;
- }
-
- private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
- if (outRect == null) {
- outRect = new Rect();
- }
- if (child == null) {
- outRect.set(0, 0, 0, 0);
- return outRect;
- }
- outRect.left = child.getLeft();
- outRect.right = child.getRight();
- outRect.top = child.getTop();
- outRect.bottom = child.getBottom();
-
- ViewParent parent = child.getParent();
- while (parent instanceof ViewGroup && parent != this) {
- final ViewGroup group = (ViewGroup) parent;
- outRect.left += group.getLeft();
- outRect.right += group.getRight();
- outRect.top += group.getTop();
- outRect.bottom += group.getBottom();
-
- parent = group.getParent();
- }
- return outRect;
- }
-
- boolean pageUp() {
- if (mCurItem > 0) {
- setCurrentItem(mCurItem-1, true);
- return true;
- }
- return false;
- }
-
- boolean pageDown() {
- if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
- setCurrentItem(mCurItem+1, true);
- return true;
- }
- return false;
- }
-
- /**
- * We only want the current page that is being shown to be focusable.
- */
- @Override
- public void addFocusables(ArrayList views, int direction, int focusableMode) {
- final int focusableCount = views.size();
-
- final int descendantFocusability = getDescendantFocusability();
-
- if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
- for (int i = 0; i < getChildCount(); i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == VISIBLE) {
- ItemInfo ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem) {
- child.addFocusables(views, direction, focusableMode);
- }
- }
- }
- }
-
- // we add ourselves (if focusable) in all cases except for when we are
- // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
- // to avoid the focus search finding layouts when a more precise search
- // among the focusable children would be more interesting.
- if (
- descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
- // No focusable descendants
- (focusableCount == views.size())) {
- // Note that we can't call the superclass here, because it will
- // add all views in. So we need to do the same thing View does.
- if (!isFocusable()) {
- return;
- }
- if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
- isInTouchMode() && !isFocusableInTouchMode()) {
- return;
- }
- if (views != null) {
- views.add(this);
- }
- }
- }
-
- /**
- * We only want the current page that is being shown to be touchable.
- */
- @Override
- public void addTouchables(ArrayList views) {
- // Note that we don't call super.addTouchables(), which means that
- // we don't call View.addTouchables(). This is okay because a ViewPager
- // is itself not touchable.
- for (int i = 0; i < getChildCount(); i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == VISIBLE) {
- ItemInfo ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem) {
- child.addTouchables(views);
- }
- }
- }
- }
-
- /**
- * We only want the current page that is being shown to be focusable.
- */
- @Override
- protected boolean onRequestFocusInDescendants(int direction,
- Rect previouslyFocusedRect) {
- int index;
- int increment;
- int end;
- int count = getChildCount();
- if ((direction & FOCUS_FORWARD) != 0) {
- index = 0;
- increment = 1;
- end = count;
- } else {
- index = count - 1;
- increment = -1;
- end = -1;
- }
- for (int i = index; i != end; i += increment) {
- View child = getChildAt(i);
- if (child.getVisibility() == VISIBLE) {
- ItemInfo ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem && child.requestFocus(direction, previouslyFocusedRect)) {
- return true;
- }
- }
- }
- return false;
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- // Dispatch scroll events from this ViewPager.
- if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
- return super.dispatchPopulateAccessibilityEvent(event);
- }
-
- // Dispatch all other accessibility events from the current page.
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == VISIBLE) {
- final ItemInfo ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem &&
- child.dispatchPopulateAccessibilityEvent(event)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- @Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams();
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return generateDefaultLayoutParams();
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams && super.checkLayoutParams(p);
- }
-
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
-
- @Override
- public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(host, event);
- event.setClassName(VerticalViewPagerImpl.class.getName());
- final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
- recordCompat.setScrollable(canScroll());
- if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
- && mAdapter != null) {
- recordCompat.setItemCount(mAdapter.getCount());
- recordCompat.setFromIndex(mCurItem);
- recordCompat.setToIndex(mCurItem);
- }
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.setClassName(VerticalViewPagerImpl.class.getName());
- info.setScrollable(canScroll());
- if (internalCanScrollVertically(1)) {
- info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
- }
- if (internalCanScrollVertically(-1)) {
- info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
- }
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (super.performAccessibilityAction(host, action, args)) {
- return true;
- }
- switch (action) {
- case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
- if (internalCanScrollVertically(1)) {
- setCurrentItem(mCurItem + 1);
- return true;
- }
- } return false;
- case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
- if (internalCanScrollVertically(-1)) {
- setCurrentItem(mCurItem - 1);
- return true;
- }
- } return false;
- default:
- break;
- }
- return false;
- }
-
- private boolean canScroll() {
- return (mAdapter != null) && (mAdapter.getCount() > 1);
- }
- }
-
- private class PagerObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- dataSetChanged();
- }
- @Override
- public void onInvalidated() {
- dataSetChanged();
- }
- }
-
- /**
- * Layout parameters that should be supplied for views added to a
- * ViewPager.
- */
- public static class LayoutParams extends ViewGroup.LayoutParams {
- /**
- * true if this view is a decoration on the pager itself and not
- * a view supplied by the adapter.
- */
- public boolean isDecor;
-
- /**
- * Gravity setting for use on decor views only:
- * Where to position the view page within the overall ViewPager
- * container; constants are defined in {@link android.view.Gravity}.
- */
- public int gravity;
-
- /**
- * Width as a 0-1 multiplier of the measured pager width
- */
- private float heightFactor = 0.f;
-
- /**
- * true if this view was added during layout and needs to be measured
- * before being positioned.
- */
- private boolean needsMeasure;
-
- /**
- * Adapter position this view is for if !isDecor
- */
- private int position;
-
- /**
- * Current child index within the ViewPager that this view occupies
- */
- private int childIndex;
-
- public LayoutParams() {
- super(FILL_PARENT, FILL_PARENT);
- }
-
- public LayoutParams(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
- gravity = a.getInteger(0, Gravity.TOP);
- a.recycle();
- }
- }
-
- static class ViewPositionComparator implements Comparator {
- @Override
- public int compare(View lhs, View rhs) {
- final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
- final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
- if (llp.isDecor != rlp.isDecor) {
- return llp.isDecor ? 1 : -1;
- }
- return llp.position - rlp.position;
- }
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt
index 6871e6bab..e215646c1 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt
@@ -1,78 +1,172 @@
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
+import android.support.v7.util.DiffUtil
import android.support.v7.widget.RecyclerView
-import android.view.View
import android.view.ViewGroup
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.source.model.Page
-import eu.kanade.tachiyomi.util.inflate
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
+import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
+import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
/**
- * Adapter of pages for a RecyclerView.
- *
- * @param fragment the fragment containing this adapter.
+ * RecyclerView Adapter used by this [viewer] to where [ViewerChapters] updates are posted.
*/
-class WebtoonAdapter(val fragment: WebtoonReader) : RecyclerView.Adapter() {
+class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter() {
/**
- * Pages stored in the adapter.
+ * List of currently set items.
*/
- var pages: List? = null
+ var items: List = emptyList()
+ private set
/**
- * Touch listener for images in holders.
+ * Updates this adapter with the given [chapters]. It handles setting a few pages of the
+ * next/previous chapter to allow seamless transitions.
*/
- val touchListener = View.OnTouchListener { _, ev -> fragment.imageGestureDetector.onTouchEvent(ev) }
+ fun setChapters(chapters: ViewerChapters) {
+ val newItems = mutableListOf()
+
+ // Add previous chapter pages and transition.
+ if (chapters.prevChapter != null) {
+ // We only need to add the last few pages of the previous chapter, because it'll be
+ // selected as the current chapter when one of those pages is selected.
+ val prevPages = chapters.prevChapter.pages
+ if (prevPages != null) {
+ newItems.addAll(prevPages.takeLast(2))
+ }
+ }
+ newItems.add(ChapterTransition.Prev(chapters.currChapter, chapters.prevChapter))
+
+ // Add current chapter.
+ val currPages = chapters.currChapter.pages
+ if (currPages != null) {
+ newItems.addAll(currPages)
+ }
+
+ // Add next chapter transition and pages.
+ newItems.add(ChapterTransition.Next(chapters.currChapter, chapters.nextChapter))
+ if (chapters.nextChapter != null) {
+ // Add at most two pages, because this chapter will be selected before the user can
+ // swap more pages.
+ val nextPages = chapters.nextChapter.pages
+ if (nextPages != null) {
+ newItems.addAll(nextPages.take(2))
+ }
+ }
+
+ val result = DiffUtil.calculateDiff(Callback(items, newItems))
+ items = newItems
+ result.dispatchUpdatesTo(this)
+ }
/**
- * Returns the number of pages.
- *
- * @return the number of pages or 0 if the list is null.
+ * Returns the amount of items of the adapter.
*/
override fun getItemCount(): Int {
- return pages?.size ?: 0
+ return items.size
}
/**
- * Returns a page given the position.
- *
- * @param position the position of the page.
- * @return the page.
+ * Returns the view type for the item at the given [position].
*/
- fun getItem(position: Int): Page {
- return pages!![position]
+ override fun getItemViewType(position: Int): Int {
+ val item = items[position]
+ return when (item) {
+ is ReaderPage -> PAGE_VIEW
+ is ChapterTransition -> TRANSITION_VIEW
+ else -> error("Unknown view type for ${item.javaClass}")
+ }
}
/**
- * 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.
+ * Creates a new view holder for an item with the given [viewType].
*/
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WebtoonHolder {
- val v = parent.inflate(R.layout.reader_webtoon_item)
- return WebtoonHolder(v, this)
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return when (viewType) {
+ PAGE_VIEW -> {
+ val view = FrameLayout(parent.context)
+ WebtoonPageHolder(view, viewer)
+ }
+ TRANSITION_VIEW -> {
+ val view = LinearLayout(parent.context)
+ WebtoonTransitionHolder(view, viewer)
+ }
+ else -> error("Unknown view type")
+ }
}
/**
- * Binds a holder with a new position.
- *
- * @param holder the holder to bind.
- * @param position the position to bind.
+ * Binds an existing view [holder] with the item at the given [position].
*/
- override fun onBindViewHolder(holder: WebtoonHolder, position: Int) {
- val page = getItem(position)
- holder.onSetValues(page)
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val item = items[position]
+ when (holder) {
+ is WebtoonPageHolder -> holder.bind(item as ReaderPage)
+ is WebtoonTransitionHolder -> holder.bind(item as ChapterTransition)
+ }
}
/**
- * Recycles the view holder.
- *
- * @param holder the holder to recycle.
+ * Recycles an existing view [holder] before adding it to the view pool.
*/
- override fun onViewRecycled(holder: WebtoonHolder) {
- holder.onRecycle()
+ override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
+ when (holder) {
+ is WebtoonPageHolder -> holder.recycle()
+ is WebtoonTransitionHolder -> holder.recycle()
+ }
+ }
+
+ /**
+ * Diff util callback used to dispatch delta updates instead of full dataset changes.
+ */
+ private class Callback(
+ private val oldItems: List,
+ private val newItems: List
+ ) : DiffUtil.Callback() {
+
+ /**
+ * Returns true if these two items are the same.
+ */
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ val oldItem = oldItems[oldItemPosition]
+ val newItem = newItems[newItemPosition]
+
+ return oldItem == newItem
+ }
+
+ /**
+ * Returns true if the contents of the items are the same.
+ */
+ override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return true
+ }
+
+ /**
+ * Returns the size of the old list.
+ */
+ override fun getOldListSize(): Int {
+ return oldItems.size
+ }
+
+ /**
+ * Returns the size of the new list.
+ */
+ override fun getNewListSize(): Int {
+ return newItems.size
+ }
+ }
+
+ private companion object {
+ /**
+ * View holder type of a chapter page view.
+ */
+ const val PAGE_VIEW = 0
+
+ /**
+ * View holder type of a chapter transition view.
+ */
+ const val TRANSITION_VIEW = 1
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonBaseHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonBaseHolder.kt
new file mode 100644
index 000000000..293127cb3
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonBaseHolder.kt
@@ -0,0 +1,46 @@
+package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup.LayoutParams
+import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder
+import rx.Subscription
+
+abstract class WebtoonBaseHolder(
+ view: View,
+ protected val viewer: WebtoonViewer
+) : BaseViewHolder(view) {
+
+ /**
+ * Context getter because it's used often.
+ */
+ val context: Context get() = itemView.context
+
+ /**
+ * Called when the view is recycled and being added to the view pool.
+ */
+ open fun recycle() {}
+
+ /**
+ * Adds a subscription to a list of subscriptions that will automatically unsubscribe when the
+ * activity or the reader is destroyed.
+ */
+ protected fun addSubscription(subscription: Subscription?) {
+ viewer.subscriptions.add(subscription)
+ }
+
+ /**
+ * Removes a subscription from the list of subscriptions.
+ */
+ protected fun removeSubscription(subscription: Subscription?) {
+ subscription?.let { viewer.subscriptions.remove(it) }
+ }
+
+ /**
+ * Extension method to set layout params to wrap content on this view.
+ */
+ protected fun View.wrapContent() {
+ layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt
new file mode 100644
index 000000000..7ac8a220a
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt
@@ -0,0 +1,74 @@
+package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
+
+import com.f2prateek.rx.preferences.Preference
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.util.addTo
+import rx.subscriptions.CompositeSubscription
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+
+/**
+ * Configuration used by webtoon viewers.
+ */
+class WebtoonConfig(preferences: PreferencesHelper = Injekt.get()) {
+
+ private val subscriptions = CompositeSubscription()
+
+ var imagePropertyChangedListener: (() -> Unit)? = null
+
+ var tappingEnabled = true
+ private set
+
+ var longTapEnabled = true
+ private set
+
+ var volumeKeysEnabled = false
+ private set
+
+ var volumeKeysInverted = false
+ private set
+
+ var imageCropBorders = false
+ private set
+
+ var doubleTapAnimDuration = 500
+ private set
+
+ init {
+ preferences.readWithTapping()
+ .register({ tappingEnabled = it })
+
+ preferences.readWithLongTap()
+ .register({ longTapEnabled = it })
+
+ preferences.cropBordersWebtoon()
+ .register({ imageCropBorders = it }, { imagePropertyChangedListener?.invoke() })
+
+ preferences.doubleTapAnimSpeed()
+ .register({ doubleTapAnimDuration = it })
+
+ preferences.readWithVolumeKeys()
+ .register({ volumeKeysEnabled = it })
+
+ preferences.readWithVolumeKeysInverted()
+ .register({ volumeKeysInverted = it })
+ }
+
+ fun unsubscribe() {
+ subscriptions.unsubscribe()
+ }
+
+ private fun Preference.register(
+ valueAssignment: (T) -> Unit,
+ onChanged: (T) -> Unit = {}
+ ) {
+ asObservable()
+ .doOnNext(valueAssignment)
+ .skip(1)
+ .distinctUntilChanged()
+ .doOnNext(onChanged)
+ .subscribe()
+ .addTo(subscriptions)
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonFrame.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonFrame.kt
new file mode 100644
index 000000000..955dc898e
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonFrame.kt
@@ -0,0 +1,80 @@
+package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
+
+import android.content.Context
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.ScaleGestureDetector
+import android.widget.FrameLayout
+
+/**
+ * Frame layout which contains a [WebtoonRecyclerView]. It's needed to handle touch events,
+ * because the recyclerview is scaled and its touch events are translated, which breaks the
+ * detectors.
+ *
+ * TODO consider integrating this class into [WebtoonViewer].
+ */
+class WebtoonFrame(context: Context) : FrameLayout(context) {
+
+ /**
+ * Scale detector, either with pinch or quick scale.
+ */
+ private val scaleDetector = ScaleGestureDetector(context, ScaleListener())
+
+ /**
+ * Fling detector.
+ */
+ private val flingDetector = GestureDetector(context, FlingListener())
+
+ /**
+ * Recycler view added in this frame.
+ */
+ private val recycler: WebtoonRecyclerView?
+ get() = getChildAt(0) as? WebtoonRecyclerView
+
+ /**
+ * Dispatches a touch event to the detectors.
+ */
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ scaleDetector.onTouchEvent(ev)
+ flingDetector.onTouchEvent(ev)
+ return super.dispatchTouchEvent(ev)
+ }
+
+ /**
+ * Scale listener used to delegate events to the recycler view.
+ */
+ inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
+ recycler?.onScaleBegin()
+ return true
+ }
+
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ recycler?.onScale(detector.scaleFactor)
+ return true
+ }
+
+ override fun onScaleEnd(detector: ScaleGestureDetector) {
+ recycler?.onScaleEnd()
+ }
+ }
+
+ /**
+ * Fling listener used to delegate events to the recycler view.
+ */
+ inner class FlingListener : GestureDetector.SimpleOnGestureListener() {
+ override fun onDown(e: MotionEvent?): Boolean {
+ return true
+ }
+
+ override fun onFling(
+ e1: MotionEvent?,
+ e2: MotionEvent?,
+ velocityX: Float,
+ velocityY: Float
+ ): Boolean {
+ return recycler?.zoomFling(velocityX.toInt(), velocityY.toInt()) ?: false
+ }
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt
deleted file mode 100755
index 34488d65e..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt
+++ /dev/null
@@ -1,316 +0,0 @@
-package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
-
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.widget.FrameLayout
-import com.davemorrissey.labs.subscaleview.ImageSource
-import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
-import com.hippo.unifile.UniFile
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.source.model.Page
-import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder
-import eu.kanade.tachiyomi.ui.reader.ReaderActivity
-import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout
-import eu.kanade.tachiyomi.util.inflate
-import kotlinx.android.synthetic.main.reader_webtoon_item.*
-import rx.Observable
-import rx.Subscription
-import rx.android.schedulers.AndroidSchedulers
-import rx.subjects.PublishSubject
-import rx.subjects.SerializedSubject
-import java.util.concurrent.TimeUnit
-
-/**
- * Holder for webtoon reader for a single page of a chapter.
- * All the elements from the layout file "reader_webtoon_item" 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) :
- BaseViewHolder(view) {
-
- /**
- * Page of a chapter.
- */
- private var page: Page? = null
-
- /**
- * Subscription for status changes of the page.
- */
- private var statusSubscription: Subscription? = null
-
- /**
- * Subscription for progress changes of the page.
- */
- private var progressSubscription: Subscription? = null
-
- /**
- * Layout of decode error.
- */
- private var decodeErrorLayout: View? = null
-
- init {
- with(image_view) {
- setMaxTileSize(readerActivity.maxBitmapSize)
- setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED)
- setDoubleTapZoomDuration(webtoonReader.doubleTapAnimDuration.toInt())
- setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
- setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH)
- setMinimumDpi(90)
- setMinimumTileDpi(180)
- setRegionDecoderClass(webtoonReader.regionDecoderClass)
- setBitmapDecoderClass(webtoonReader.bitmapDecoderClass)
- setCropBorders(webtoonReader.cropBorders)
- setVerticalScrollingParent(true)
- setOnTouchListener(adapter.touchListener)
- setOnLongClickListener { webtoonReader.onLongClick(page) }
- setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
- override fun onReady() {
- onImageDecoded()
- }
-
- override fun onImageLoadError(e: Exception) {
- onImageDecodeError()
- }
- })
- }
-
- progress_container.layoutParams = FrameLayout.LayoutParams(
- MATCH_PARENT, webtoonReader.screenHeight)
-
- view.setOnTouchListener(adapter.touchListener)
- retry_button.setOnTouchListener { _, 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) {
- this.page = page
- observeStatus()
- }
-
- /**
- * Called when the view is recycled and added to the view pool.
- */
- fun onRecycle() {
- unsubscribeStatus()
- unsubscribeProgress()
- decodeErrorLayout?.let {
- (view as ViewGroup).removeView(it)
- decodeErrorLayout = null
- }
- image_view.recycle()
- image_view.visibility = View.GONE
- progress_container.visibility = View.VISIBLE
- }
-
- /**
- * Observes the status of the page and notify the changes.
- *
- * @see processStatus
- */
- private fun observeStatus() {
- unsubscribeStatus()
-
- val page = page ?: return
-
- val statusSubject = SerializedSubject(PublishSubject.create())
- page.setStatusSubject(statusSubject)
-
- statusSubscription = statusSubject.startWith(page.status)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe { processStatus(it) }
-
- addSubscription(statusSubscription)
- }
-
- /**
- * Observes the progress of the page and updates view.
- */
- private fun observeProgress() {
- unsubscribeProgress()
-
- val page = page ?: return
-
- progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
- .map { page.progress }
- .distinctUntilChanged()
- .onBackpressureLatest()
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe { progress ->
- progress_text.text = if (progress > 0) {
- view.context.getString(R.string.download_progress, progress)
- } else {
- view.context.getString(R.string.downloading)
- }
- }
-
- addSubscription(progressSubscription)
- }
-
- /**
- * 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 -> setQueued()
- Page.LOAD_PAGE -> setLoading()
- Page.DOWNLOAD_IMAGE -> {
- observeProgress()
- setDownloading()
- }
- Page.READY -> {
- setImage()
- unsubscribeProgress()
- }
- Page.ERROR -> {
- setError()
- unsubscribeProgress()
- }
- }
- }
-
- /**
- * Adds a subscription to a list of subscriptions that will automatically unsubscribe when the
- * activity or the reader is destroyed.
- */
- private fun addSubscription(subscription: Subscription?) {
- webtoonReader.subscriptions.add(subscription)
- }
-
- /**
- * Removes a subscription from the list of subscriptions.
- */
- private fun removeSubscription(subscription: Subscription?) {
- subscription?.let { webtoonReader.subscriptions.remove(it) }
- }
-
- /**
- * Unsubscribes from the status subscription.
- */
- private fun unsubscribeStatus() {
- page?.setStatusSubject(null)
- removeSubscription(statusSubscription)
- statusSubscription = null
- }
-
- /**
- * Unsubscribes from the progress subscription.
- */
- private fun unsubscribeProgress() {
- removeSubscription(progressSubscription)
- progressSubscription = null
- }
-
- /**
- * Called when the page is queued.
- */
- private fun setQueued() = with(view) {
- progress_container.visibility = View.VISIBLE
- progress_text.visibility = View.INVISIBLE
- retry_container.visibility = View.GONE
- decodeErrorLayout?.let {
- (view as ViewGroup).removeView(it)
- decodeErrorLayout = null
- }
- }
-
- /**
- * Called when the page is loading.
- */
- private fun setLoading() = with(view) {
- progress_container.visibility = View.VISIBLE
- progress_text.visibility = View.VISIBLE
- progress_text.setText(R.string.downloading)
- }
-
- /**
- * Called when the page is downloading
- */
- private fun setDownloading() = with(view) {
- progress_container.visibility = View.VISIBLE
- progress_text.visibility = View.VISIBLE
- }
-
- /**
- * Called when the page is ready.
- */
- private fun setImage() = with(view) {
- val uri = page?.uri
- if (uri == null) {
- page?.status = Page.ERROR
- return
- }
-
- val file = UniFile.fromUri(context, uri)
- if (!file.exists()) {
- page?.status = Page.ERROR
- return
- }
-
- progress_text.visibility = View.INVISIBLE
- image_view.visibility = View.VISIBLE
- image_view.setImage(ImageSource.uri(file.uri))
- }
-
- /**
- * Called when the page has an error.
- */
- private fun setError() = with(view) {
- progress_container.visibility = View.GONE
- retry_container.visibility = View.VISIBLE
- }
-
- /**
- * Called when the image is decoded and going to be displayed.
- */
- private fun onImageDecoded() {
- progress_container.visibility = View.GONE
- }
-
- /**
- * Called when the image fails to decode.
- */
- private fun onImageDecodeError() {
- progress_container.visibility = View.GONE
-
- val page = page ?: return
- if (decodeErrorLayout != null || !webtoonReader.isAdded) return
-
- val layout = (view as ViewGroup).inflate(R.layout.reader_page_decode_error)
- PageDecodeErrorLayout(layout, page, readerActivity.readerTheme, {
- if (webtoonReader.isAdded) {
- readerActivity.presenter.retryPage(page)
- }
- })
- decodeErrorLayout = layout
- view.addView(layout)
- }
-
- /**
- * Property to get the reader activity.
- */
- private val readerActivity: ReaderActivity
- get() = adapter.fragment.readerActivity
-
- /**
- * Property to get the webtoon reader.
- */
- private val webtoonReader: WebtoonReader
- get() = adapter.fragment
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt
new file mode 100644
index 000000000..c9cd0712c
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt
@@ -0,0 +1,55 @@
+@file:Suppress("PackageDirectoryMismatch")
+
+package android.support.v7.widget
+
+import android.support.v7.widget.RecyclerView.NO_POSITION
+import eu.kanade.tachiyomi.ui.reader.ReaderActivity
+
+/**
+ * Layout manager used by the webtoon viewer. Item prefetch is disabled because the extra layout
+ * space feature is used which allows setting the image even if the holder is not visible,
+ * avoiding (in most cases) black views when they are visible.
+ *
+ * This layout manager uses the same package name as the support library in order to use a package
+ * protected method.
+ */
+class WebtoonLayoutManager(activity: ReaderActivity) : LinearLayoutManager(activity) {
+
+ /**
+ * Extra layout space is set to half the screen height.
+ */
+ private val extraLayoutSpace = activity.resources.displayMetrics.heightPixels / 2
+
+ init {
+ isItemPrefetchEnabled = false
+ }
+
+ /**
+ * Returns the custom extra layout space.
+ */
+ override fun getExtraLayoutSpace(state: RecyclerView.State): Int {
+ return extraLayoutSpace
+ }
+
+ /**
+ * Returns the position of the last item whose end side is visible on screen.
+ */
+ fun findLastEndVisibleItemPosition(): Int {
+ ensureLayoutState()
+ @ViewBoundsCheck.ViewBounds val preferredBoundsFlag =
+ (ViewBoundsCheck.FLAG_CVE_LT_PVE or ViewBoundsCheck.FLAG_CVE_EQ_PVE)
+
+ val fromIndex = childCount - 1
+ val toIndex = -1
+
+ val child = if (mOrientation == HORIZONTAL)
+ mHorizontalBoundCheck
+ .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
+ else
+ mVerticalBoundCheck
+ .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
+
+ return if (child == null) NO_POSITION else getPosition(child)
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt
new file mode 100644
index 000000000..40ab711a4
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt
@@ -0,0 +1,507 @@
+package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.support.v7.widget.AppCompatButton
+import android.support.v7.widget.AppCompatImageView
+import android.view.Gravity
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.load.engine.GlideException
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
+import com.bumptech.glide.request.RequestListener
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.request.transition.NoTransition
+import com.davemorrissey.labs.subscaleview.ImageSource
+import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.glide.GlideApp
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
+import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
+import eu.kanade.tachiyomi.util.ImageUtil
+import eu.kanade.tachiyomi.util.dpToPx
+import eu.kanade.tachiyomi.util.gone
+import eu.kanade.tachiyomi.util.visible
+import rx.Observable
+import rx.Subscription
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
+import java.io.InputStream
+import java.util.concurrent.TimeUnit
+
+/**
+ * Holder of the webtoon reader for a single page of a chapter.
+ *
+ * @param frame the root view for this holder.
+ * @param viewer the webtoon viewer.
+ * @constructor creates a new webtoon holder.
+ */
+class WebtoonPageHolder(
+ private val frame: FrameLayout,
+ viewer: WebtoonViewer
+) : WebtoonBaseHolder(frame, viewer) {
+
+ /**
+ * Loading progress bar to indicate the current progress.
+ */
+ private val progressBar = createProgressBar()
+
+ /**
+ * Progress bar container. Needed to keep a minimum height size of the holder, otherwise the
+ * adapter would create more views to fill the screen, which is not wanted.
+ */
+ private lateinit var progressContainer: ViewGroup
+
+ /**
+ * Image view that supports subsampling on zoom.
+ */
+ private var subsamplingImageView: SubsamplingScaleImageView? = null
+
+ /**
+ * Simple image view only used on GIFs.
+ */
+ private var imageView: ImageView? = null
+
+ /**
+ * Retry button container used to allow retrying.
+ */
+ private var retryContainer: ViewGroup? = null
+
+ /**
+ * Error layout to show when the image fails to decode.
+ */
+ private var decodeErrorLayout: ViewGroup? = null
+
+ /**
+ * Getter to retrieve the height of the recycler view.
+ */
+ private val parentHeight
+ get() = viewer.recycler.height
+
+ /**
+ * Page of a chapter.
+ */
+ private var page: ReaderPage? = null
+
+ /**
+ * Subscription for status changes of the page.
+ */
+ private var statusSubscription: Subscription? = null
+
+ /**
+ * Subscription for progress changes of the page.
+ */
+ private var progressSubscription: Subscription? = null
+
+ /**
+ * Subscription used to read the header of the image. This is needed in order to instantiate
+ * the appropiate image view depending if the image is animated (GIF).
+ */
+ private var readImageHeaderSubscription: Subscription? = null
+
+ init {
+ frame.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
+ }
+
+ /**
+ * Binds the given [page] with this view holder, subscribing to its state.
+ */
+ fun bind(page: ReaderPage) {
+ this.page = page
+ observeStatus()
+ }
+
+ /**
+ * Called when the view is recycled and added to the view pool.
+ */
+ override fun recycle() {
+ unsubscribeStatus()
+ unsubscribeProgress()
+ unsubscribeReadImageHeader()
+
+ removeDecodeErrorLayout()
+ subsamplingImageView?.recycle()
+ subsamplingImageView?.gone()
+ imageView?.let { GlideApp.with(frame).clear(it) }
+ imageView?.gone()
+ progressBar.setProgress(0)
+ }
+
+ /**
+ * Observes the status of the page and notify the changes.
+ *
+ * @see processStatus
+ */
+ private fun observeStatus() {
+ unsubscribeStatus()
+
+ val page = page ?: return
+ val loader = page.chapter.pageLoader ?: return
+ statusSubscription = loader.getPage(page)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe { processStatus(it) }
+
+ addSubscription(statusSubscription)
+ }
+
+ /**
+ * Observes the progress of the page and updates view.
+ */
+ private fun observeProgress() {
+ unsubscribeProgress()
+
+ val page = page ?: return
+
+ progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
+ .map { page.progress }
+ .distinctUntilChanged()
+ .onBackpressureLatest()
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe { value -> progressBar.setProgress(value) }
+
+ addSubscription(progressSubscription)
+ }
+
+ /**
+ * 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 -> setQueued()
+ Page.LOAD_PAGE -> setLoading()
+ Page.DOWNLOAD_IMAGE -> {
+ observeProgress()
+ setDownloading()
+ }
+ Page.READY -> {
+ setImage()
+ unsubscribeProgress()
+ }
+ Page.ERROR -> {
+ setError()
+ unsubscribeProgress()
+ }
+ }
+ }
+
+ /**
+ * Unsubscribes from the status subscription.
+ */
+ private fun unsubscribeStatus() {
+ removeSubscription(statusSubscription)
+ statusSubscription = null
+ }
+
+ /**
+ * Unsubscribes from the progress subscription.
+ */
+ private fun unsubscribeProgress() {
+ removeSubscription(progressSubscription)
+ progressSubscription = null
+ }
+
+ /**
+ * Unsubscribes from the read image header subscription.
+ */
+ private fun unsubscribeReadImageHeader() {
+ removeSubscription(readImageHeaderSubscription)
+ readImageHeaderSubscription = null
+ }
+
+ /**
+ * Called when the page is queued.
+ */
+ private fun setQueued() {
+ progressContainer.visible()
+ progressBar.visible()
+ retryContainer?.gone()
+ removeDecodeErrorLayout()
+ }
+
+ /**
+ * Called when the page is loading.
+ */
+ private fun setLoading() {
+ progressContainer.visible()
+ progressBar.visible()
+ retryContainer?.gone()
+ removeDecodeErrorLayout()
+ }
+
+ /**
+ * Called when the page is downloading
+ */
+ private fun setDownloading() {
+ progressContainer.visible()
+ progressBar.visible()
+ retryContainer?.gone()
+ removeDecodeErrorLayout()
+ }
+
+ /**
+ * Called when the page is ready.
+ */
+ private fun setImage() {
+ progressContainer.visible()
+ progressBar.visible()
+ progressBar.completeAndFadeOut()
+ retryContainer?.gone()
+ removeDecodeErrorLayout()
+
+ unsubscribeReadImageHeader()
+ val streamFn = page?.stream ?: return
+
+ var openStream: InputStream? = null
+ readImageHeaderSubscription = Observable
+ .fromCallable {
+ val stream = streamFn().buffered(16)
+ openStream = stream
+
+ ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
+ }
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnNext { isAnimated ->
+ if (!isAnimated) {
+ val subsamplingView = initSubsamplingImageView()
+ subsamplingView.visible()
+ subsamplingView.setImage(ImageSource.inputStream(openStream!!))
+ } else {
+ val imageView = initImageView()
+ imageView.visible()
+ imageView.setImage(openStream!!)
+ }
+ }
+ // Keep the Rx stream alive to close the input stream only when unsubscribed
+ .flatMap { Observable.never() }
+ .doOnUnsubscribe { openStream?.close() }
+ .subscribe({}, {})
+
+ addSubscription(readImageHeaderSubscription)
+ }
+
+ /**
+ * Called when the page has an error.
+ */
+ private fun setError() {
+ progressContainer.gone()
+ initRetryLayout().visible()
+ }
+
+ /**
+ * Called when the image is decoded and going to be displayed.
+ */
+ private fun onImageDecoded() {
+ progressContainer.gone()
+ }
+
+ /**
+ * Called when the image fails to decode.
+ */
+ private fun onImageDecodeError() {
+ progressContainer.gone()
+ initDecodeErrorLayout().visible()
+ }
+
+ /**
+ * Creates a new progress bar.
+ */
+ @SuppressLint("PrivateResource")
+ private fun createProgressBar(): ReaderProgressBar {
+ progressContainer = FrameLayout(context)
+ frame.addView(progressContainer, MATCH_PARENT, parentHeight)
+
+ val progress = ReaderProgressBar(context).apply {
+ val size = 48.dpToPx
+ layoutParams = FrameLayout.LayoutParams(size, size).apply {
+ gravity = Gravity.CENTER_HORIZONTAL
+ setMargins(0, parentHeight/4, 0, 0)
+ }
+ }
+ progressContainer.addView(progress)
+ return progress
+ }
+
+ /**
+ * Initializes a subsampling scale view.
+ */
+ private fun initSubsamplingImageView(): SubsamplingScaleImageView {
+ if (subsamplingImageView != null) return subsamplingImageView!!
+
+ val config = viewer.config
+
+ subsamplingImageView = WebtoonSubsamplingImageView(context).apply {
+ setMaxTileSize(viewer.activity.maxBitmapSize)
+ setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
+ setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH)
+ setMinimumDpi(90)
+ setMinimumTileDpi(180)
+ setCropBorders(config.imageCropBorders)
+ setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
+ override fun onReady() {
+ onImageDecoded()
+ }
+
+ override fun onImageLoadError(e: Exception) {
+ onImageDecodeError()
+ }
+ })
+ }
+ frame.addView(subsamplingImageView, MATCH_PARENT, MATCH_PARENT)
+ return subsamplingImageView!!
+ }
+
+ /**
+ * Initializes an image view, used for GIFs.
+ */
+ private fun initImageView(): ImageView {
+ if (imageView != null) return imageView!!
+
+ imageView = AppCompatImageView(context).apply {
+ adjustViewBounds = true
+ }
+ frame.addView(imageView, MATCH_PARENT, MATCH_PARENT)
+ return imageView!!
+ }
+
+ /**
+ * Initializes a button to retry pages.
+ */
+ private fun initRetryLayout(): ViewGroup {
+ if (retryContainer != null) return retryContainer!!
+
+ retryContainer = FrameLayout(context)
+ frame.addView(retryContainer, MATCH_PARENT, parentHeight)
+
+ AppCompatButton(context).apply {
+ layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
+ gravity = Gravity.CENTER_HORIZONTAL
+ setMargins(0, parentHeight/4, 0, 0)
+ }
+ setText(R.string.action_retry)
+ setOnClickListener {
+ page?.let { it.chapter.pageLoader?.retryPage(it) }
+ }
+
+ retryContainer!!.addView(this)
+ }
+ return retryContainer!!
+ }
+
+ /**
+ * Initializes a decode error layout.
+ */
+ private fun initDecodeErrorLayout(): ViewGroup {
+ if (decodeErrorLayout != null) return decodeErrorLayout!!
+
+ val margins = 8.dpToPx
+
+ val decodeLayout = LinearLayout(context).apply {
+ layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, parentHeight).apply {
+ setMargins(0, parentHeight/6, 0, 0)
+ }
+ gravity = Gravity.CENTER_HORIZONTAL
+ orientation = LinearLayout.VERTICAL
+ }
+ decodeErrorLayout = decodeLayout
+
+ TextView(context).apply {
+ layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
+ setMargins(0, margins, 0, margins)
+ }
+ gravity = Gravity.CENTER
+ setText(R.string.decode_image_error)
+
+ decodeLayout.addView(this)
+ }
+
+ AppCompatButton(context).apply {
+ layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
+ setMargins(0, margins, 0, margins)
+ }
+ setText(R.string.action_retry)
+ setOnClickListener {
+ page?.let { it.chapter.pageLoader?.retryPage(it) }
+ }
+
+ decodeLayout.addView(this)
+ }
+
+ val imageUrl = page?.imageUrl
+ if (imageUrl.orEmpty().startsWith("http")) {
+ AppCompatButton(context).apply {
+ layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
+ setMargins(0, margins, 0, margins)
+ }
+ setText(R.string.action_open_in_browser)
+ setOnClickListener {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(imageUrl))
+ context.startActivity(intent)
+ }
+
+ decodeLayout.addView(this)
+ }
+ }
+
+ frame.addView(decodeLayout)
+ return decodeLayout
+ }
+
+ /**
+ * Removes the decode error layout from the holder, if found.
+ */
+ private fun removeDecodeErrorLayout() {
+ val layout = decodeErrorLayout
+ if (layout != null) {
+ frame.removeView(layout)
+ decodeErrorLayout = null
+ }
+ }
+
+ /**
+ * Extension method to set a [stream] into this ImageView.
+ */
+ private fun ImageView.setImage(stream: InputStream) {
+ GlideApp.with(this)
+ .load(stream)
+ .skipMemoryCache(true)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
+ .listener(object : RequestListener {
+ override fun onLoadFailed(
+ e: GlideException?,
+ model: Any?,
+ target: Target?,
+ isFirstResource: Boolean
+ ): Boolean {
+ onImageDecodeError()
+ return false
+ }
+
+ override fun onResourceReady(
+ resource: Drawable?,
+ model: Any?,
+ target: Target?,
+ dataSource: DataSource?,
+ isFirstResource: Boolean
+ ): Boolean {
+ onImageDecoded()
+ return false
+ }
+ })
+ .into(this)
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt
deleted file mode 100755
index 3ac0c5085..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt
+++ /dev/null
@@ -1,263 +0,0 @@
-package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
-
-import android.os.Build
-import android.os.Bundle
-import android.support.v7.widget.RecyclerView
-import android.util.DisplayMetrics
-import android.view.Display
-import android.view.GestureDetector
-import android.view.LayoutInflater
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import eu.kanade.tachiyomi.source.model.Page
-import eu.kanade.tachiyomi.ui.reader.ReaderChapter
-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
-
- /**
- * Whether to crop image borders.
- */
- var cropBorders: Boolean = false
- private set
-
- /**
- * Duration of the double tap animation
- */
- var doubleTapAnimDuration = 500
- private set
-
- /**
- * Gesture detector for image touch events.
- */
- val imageGestureDetector by lazy { GestureDetector(context, ImageGestureListener()) }
-
- /**
- * Subscriptions used while the view exists.
- */
- lateinit var subscriptions: CompositeSubscription
- private set
-
- private var scrollDistance: Int = 0
-
- val screenHeight by lazy {
- val display = activity!!.windowManager.defaultDisplay
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- val metrics = DisplayMetrics()
- display.getRealMetrics(metrics)
- metrics.heightPixels
- } else {
- val field = Display::class.java.getMethod("getRawHeight")
- field.invoke(display) as Int
- }
- }
-
- 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.extraLayoutSpace = screenHeight / 2
-
- 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 index = layoutManager.findLastVisibleItemPosition()
- if (index != currentPage) {
- pages.getOrNull(index)?.let { onPageChanged(index) }
- }
- }
- })
-
- subscriptions = CompositeSubscription()
- subscriptions.add(readerActivity.preferences.imageDecoder()
- .asObservable()
- .doOnNext { setDecoderClass(it) }
- .skip(1)
- .distinctUntilChanged()
- .subscribe { refreshAdapter() })
-
- subscriptions.add(readerActivity.preferences.cropBordersWebtoon()
- .asObservable()
- .doOnNext { cropBorders = it }
- .skip(1)
- .distinctUntilChanged()
- .subscribe { refreshAdapter() })
-
- subscriptions.add(readerActivity.preferences.doubleTapAnimSpeed()
- .asObservable()
- .subscribe { doubleTapAnimDuration = it })
-
- setPagesOnAdapter()
- return recycler
- }
-
- fun refreshAdapter() {
- val activePage = layoutManager.findFirstVisibleItemPosition()
- recycler.adapter = adapter
- setActivePage(activePage)
- }
-
- /**
- * Uses two ways to scroll to the last page read.
- */
- private fun scrollToLastPageRead(page: Int) {
- // Scrolls to the correct page initially, but isn't reliable beyond that.
- recycler.addOnLayoutChangeListener(object: View.OnLayoutChangeListener {
- override fun onLayoutChange(p0: View?, p1: Int, p2: Int, p3: Int, p4: Int, p5: Int, p6: Int, p7: Int, p8: Int) {
- if(pages.isEmpty()) {
- setActivePage(page)
- } else {
- recycler.removeOnLayoutChangeListener(this)
- }
- }
- })
-
- // Scrolls to the correct page after app has been in use, but can't do it the very first time.
- recycler.post { setActivePage(page) }
- }
-
- override fun onDestroyView() {
- subscriptions.unsubscribe()
- super.onDestroyView()
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- val savedPosition = pages.getOrNull(layoutManager.findFirstVisibleItemPosition())?.index ?: 0
- outState.putInt(SAVED_POSITION, savedPosition)
- super.onSaveInstanceState(outState)
- }
-
- /**
- * Gesture detector for Subsampling Scale Image View.
- */
- inner class ImageGestureListener : GestureDetector.SimpleOnGestureListener() {
-
- override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
- if (isAdded) {
- val positionX = e.x
-
- if (positionX < recycler.width * LEFT_REGION) {
- if (tappingEnabled) moveLeft()
- } else if (positionX > recycler.width * RIGHT_REGION) {
- if (tappingEnabled) moveRight()
- } else {
- readerActivity.toggleMenu()
- }
- }
- 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: ReaderChapter, currentPage: Page) {
- this.currentPage = currentPage.index
-
- // Make sure the view is already initialized.
- if (view != null) {
- setPagesOnAdapter()
- scrollToLastPageRead(this.currentPage)
- }
- }
-
- /**
- * Called when a chapter is appended in [BaseReader].
- * @param chapter the chapter appended.
- */
- override fun onChapterAppended(chapter: ReaderChapter) {
- // 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
- onPageChanged(currentPage)
- }
- }
-
- /**
- * 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 moveRight() {
- recycler.smoothScrollBy(0, scrollDistance)
- }
-
- /**
- * Moves to the previous page or requests the previous chapter if it's the first one.
- */
- override fun moveLeft() {
- recycler.smoothScrollBy(0, -scrollDistance)
- }
-
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt
new file mode 100644
index 000000000..eae782c93
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt
@@ -0,0 +1,324 @@
+package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.annotation.TargetApi
+import android.content.Context
+import android.os.Build
+import android.support.v7.widget.LinearLayoutManager
+import android.support.v7.widget.RecyclerView
+import android.util.AttributeSet
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import android.view.animation.DecelerateInterpolator
+import eu.kanade.tachiyomi.ui.reader.viewer.GestureDetectorWithLongTap
+
+/**
+ * Implementation of a [RecyclerView] used by the webtoon reader.
+ */
+open class WebtoonRecyclerView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+) : RecyclerView(context, attrs, defStyle) {
+
+ private var isZooming = false
+ private var atLastPosition = false
+ private var atFirstPosition = false
+ private var halfWidth = 0
+ private var halfHeight = 0
+ private var firstVisibleItemPosition = 0
+ private var lastVisibleItemPosition = 0
+ private var currentScale = DEFAULT_RATE
+
+ private val listener = GestureListener()
+ private val detector = Detector()
+
+ var tapListener: ((MotionEvent) -> Unit)? = null
+ var longTapListener: ((MotionEvent) -> Boolean)? = null
+
+ override fun onMeasure(widthSpec: Int, heightSpec: Int) {
+ halfWidth = MeasureSpec.getSize(widthSpec) / 2
+ halfHeight = MeasureSpec.getSize(heightSpec) / 2
+ super.onMeasure(widthSpec, heightSpec)
+ }
+
+ override fun onTouchEvent(e: MotionEvent): Boolean {
+ detector.onTouchEvent(e)
+ return super.onTouchEvent(e)
+ }
+
+ override fun onScrolled(dx: Int, dy: Int) {
+ super.onScrolled(dx, dy)
+ val layoutManager = layoutManager
+ lastVisibleItemPosition =
+ (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
+ firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ override fun onScrollStateChanged(state: Int) {
+ super.onScrollStateChanged(state)
+ val layoutManager = layoutManager
+ val visibleItemCount = layoutManager.childCount
+ val totalItemCount = layoutManager.itemCount
+ atLastPosition = visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1
+ atFirstPosition = firstVisibleItemPosition == 0
+ }
+
+ private fun getPositionX(positionX: Float): Float {
+ val maxPositionX = halfWidth * (currentScale - 1)
+ return positionX.coerceIn(-maxPositionX, maxPositionX)
+ }
+
+ private fun getPositionY(positionY: Float): Float {
+ val maxPositionY = halfHeight * (currentScale - 1)
+ return positionY.coerceIn(-maxPositionY, maxPositionY)
+ }
+
+ private fun zoom(
+ fromRate: Float,
+ toRate: Float,
+ fromX: Float,
+ toX: Float,
+ fromY: Float,
+ toY: Float
+ ) {
+ isZooming = true
+ val animatorSet = AnimatorSet()
+ val translationXAnimator = ValueAnimator.ofFloat(fromX, toX)
+ translationXAnimator.addUpdateListener { animation -> x = animation.animatedValue as Float }
+
+ val translationYAnimator = ValueAnimator.ofFloat(fromY, toY)
+ translationYAnimator.addUpdateListener { animation -> y = animation.animatedValue as Float }
+
+ val scaleAnimator = ValueAnimator.ofFloat(fromRate, toRate)
+ scaleAnimator.addUpdateListener { animation ->
+ setScaleRate(animation.animatedValue as Float)
+ }
+ animatorSet.playTogether(translationXAnimator, translationYAnimator, scaleAnimator)
+ animatorSet.duration = ANIMATOR_DURATION_TIME.toLong()
+ animatorSet.interpolator = DecelerateInterpolator()
+ animatorSet.start()
+ animatorSet.addListener(object : Animator.AnimatorListener {
+ override fun onAnimationStart(animation: Animator) {
+
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ isZooming = false
+ currentScale = toRate
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+
+ }
+
+ override fun onAnimationRepeat(animation: Animator) {
+
+ }
+ })
+ }
+
+ fun zoomFling(velocityX: Int, velocityY: Int): Boolean {
+ if (currentScale <= 1f) return false
+
+ val distanceTimeFactor = 0.4f
+ var newX: Float? = null
+ var newY: Float? = null
+
+ if (velocityX != 0) {
+ val dx = (distanceTimeFactor * velocityX / 2)
+ newX = getPositionX(x + dx)
+ }
+ if (velocityY != 0 && (atFirstPosition || atLastPosition)) {
+ val dy = (distanceTimeFactor * velocityY / 2)
+ newY = getPositionY(y + dy)
+ }
+
+ animate()
+ .apply {
+ newX?.let { x(it) }
+ newY?.let { y(it) }
+ }
+ .setInterpolator(DecelerateInterpolator())
+ .setDuration(400)
+ .start()
+
+ return true
+ }
+
+ private fun zoomScrollBy(dx: Int, dy: Int) {
+ if (dx != 0) {
+ x = getPositionX(x + dx)
+ }
+ if (dy != 0) {
+ y = getPositionY(y + dy)
+ }
+ }
+
+ private fun setScaleRate(rate: Float) {
+ scaleX = rate
+ scaleY = rate
+ }
+
+ fun onScale(scaleFactor: Float) {
+ currentScale *= scaleFactor
+ currentScale = currentScale.coerceIn(
+ DEFAULT_RATE,
+ MAX_SCALE_RATE)
+
+ setScaleRate(currentScale)
+
+ if (currentScale != DEFAULT_RATE) {
+ x = getPositionX(x)
+ y = getPositionY(y)
+ } else {
+ x = 0f
+ y = 0f
+ }
+ }
+
+ fun onScaleBegin() {
+ if (detector.isDoubleTapping) {
+ detector.isQuickScaling = true
+ }
+ }
+
+ fun onScaleEnd() {
+ if (scaleX < DEFAULT_RATE) {
+ zoom(currentScale, DEFAULT_RATE, x, 0f, y, 0f)
+ }
+ }
+
+ inner class GestureListener : GestureDetectorWithLongTap.Listener() {
+
+ override fun onSingleTapConfirmed(ev: MotionEvent): Boolean {
+ tapListener?.invoke(ev)
+ return false
+ }
+
+ override fun onDoubleTap(ev: MotionEvent): Boolean {
+ detector.isDoubleTapping = true
+ return false
+ }
+
+ fun onDoubleTapConfirmed(ev: MotionEvent) {
+ if (!isZooming) {
+ if (scaleX != DEFAULT_RATE) {
+ zoom(currentScale, DEFAULT_RATE, x, 0f, y, 0f)
+ } else {
+ val toScale = 2f
+ val toX = (halfWidth - ev.x) * (toScale - 1)
+ val toY = (halfHeight - ev.y) * (toScale - 1)
+ zoom(DEFAULT_RATE, toScale, 0f, toX, 0f, toY)
+ }
+ }
+ }
+
+ override fun onLongTapConfirmed(ev: MotionEvent) {
+ val listener = longTapListener
+ if (listener != null && listener.invoke(ev)) {
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+ }
+ }
+
+ }
+
+ inner class Detector : GestureDetectorWithLongTap(context, listener) {
+
+ private var scrollPointerId = 0
+ private var downX = 0
+ private var downY = 0
+ private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
+ private var isZoomDragging = false
+ var isDoubleTapping = false
+ var isQuickScaling = false
+
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ val action = ev.actionMasked
+ val actionIndex = ev.actionIndex
+
+ when (action) {
+ MotionEvent.ACTION_DOWN -> {
+ scrollPointerId = ev.getPointerId(0)
+ downX = (ev.x + 0.5f).toInt()
+ downY = (ev.y + 0.5f).toInt()
+ }
+ MotionEvent.ACTION_POINTER_DOWN -> {
+ scrollPointerId = ev.getPointerId(actionIndex)
+ downX = (ev.getX(actionIndex) + 0.5f).toInt()
+ downY = (ev.getY(actionIndex) + 0.5f).toInt()
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (isDoubleTapping && isQuickScaling) {
+ return true
+ }
+
+ val index = ev.findPointerIndex(scrollPointerId)
+ if (index < 0) {
+ return false
+ }
+
+ val x = (ev.getX(index) + 0.5f).toInt()
+ val y = (ev.getY(index) + 0.5f).toInt()
+ var dx = x - downX
+ var dy = if (atFirstPosition || atLastPosition) y - downY else 0
+
+ if (!isZoomDragging && currentScale > 1f) {
+ var startScroll = false
+
+ if (Math.abs(dx) > touchSlop) {
+ if (dx < 0) {
+ dx += touchSlop
+ } else {
+ dx -= touchSlop
+ }
+ startScroll = true
+ }
+ if (Math.abs(dy) > touchSlop) {
+ if (dy < 0) {
+ dy += touchSlop
+ } else {
+ dy -= touchSlop
+ }
+ startScroll = true
+ }
+
+ if (startScroll) {
+ isZoomDragging = true
+ }
+ }
+
+ if (isZoomDragging) {
+ zoomScrollBy(dx, dy)
+ }
+ }
+ MotionEvent.ACTION_UP -> {
+ if (isDoubleTapping && !isQuickScaling) {
+ listener.onDoubleTapConfirmed(ev)
+ }
+ isZoomDragging = false
+ isDoubleTapping = false
+ isQuickScaling = false
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ isZoomDragging = false
+ isDoubleTapping = false
+ isQuickScaling = false
+ }
+ }
+ return super.onTouchEvent(ev)
+ }
+
+ }
+
+ private companion object {
+ const val ANIMATOR_DURATION_TIME = 200
+ const val DEFAULT_RATE = 1f
+ const val MAX_SCALE_RATE = 3f
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonSubsamplingImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonSubsamplingImageView.kt
new file mode 100644
index 000000000..692c0596b
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonSubsamplingImageView.kt
@@ -0,0 +1,21 @@
+package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
+
+/**
+ * Implementation of subsampling scale image view that ignores all touch events, because the
+ * webtoon viewer handles all the gestures.
+ */
+class WebtoonSubsamplingImageView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null
+) : SubsamplingScaleImageView(context, attrs) {
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ return false
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt
new file mode 100644
index 000000000..4a1812c2c
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt
@@ -0,0 +1,212 @@
+package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
+
+import android.graphics.Typeface
+import android.support.v7.widget.AppCompatButton
+import android.support.v7.widget.AppCompatTextView
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.StyleSpan
+import android.view.Gravity
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import android.widget.TextView
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
+import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
+import eu.kanade.tachiyomi.util.dpToPx
+import eu.kanade.tachiyomi.util.visibleIf
+import rx.Subscription
+import rx.android.schedulers.AndroidSchedulers
+
+/**
+ * Holder of the webtoon viewer that contains a chapter transition.
+ */
+class WebtoonTransitionHolder(
+ val layout: LinearLayout,
+ viewer: WebtoonViewer
+) : WebtoonBaseHolder(layout, viewer) {
+
+ /**
+ * Subscription for status changes of the transition page.
+ */
+ private var statusSubscription: Subscription? = null
+
+ /**
+ * Text view used to display the text of the current and next/prev chapters.
+ */
+ private var textView = TextView(context)
+
+ /**
+ * View container of the current status of the transition page. Child views will be added
+ * dynamically.
+ */
+ private var pagesContainer = LinearLayout(context).apply {
+ orientation = LinearLayout.VERTICAL
+ gravity = Gravity.CENTER
+ }
+
+ init {
+ layout.layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
+ layout.orientation = LinearLayout.VERTICAL
+ layout.gravity = Gravity.CENTER
+
+ val paddingVertical = 48.dpToPx
+ val paddingHorizontal = 32.dpToPx
+ layout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
+
+ val childMargins = 16.dpToPx
+ val childParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
+ setMargins(0, childMargins, 0, childMargins)
+ }
+
+ layout.addView(textView, childParams)
+ layout.addView(pagesContainer, childParams)
+ }
+
+ /**
+ * Binds the given [transition] with this view holder, subscribing to its state.
+ */
+ fun bind(transition: ChapterTransition) {
+ when (transition) {
+ is ChapterTransition.Prev -> bindPrevChapterTransition(transition)
+ is ChapterTransition.Next -> bindNextChapterTransition(transition)
+ }
+ }
+
+ /**
+ * Called when the view is recycled and being added to the view pool.
+ */
+ override fun recycle() {
+ unsubscribeStatus()
+ }
+
+ /**
+ * Binds a next chapter transition on this view and subscribes to the load status.
+ */
+ private fun bindNextChapterTransition(transition: ChapterTransition.Next) {
+ val nextChapter = transition.to
+
+ textView.text = if (nextChapter != null) {
+ SpannableStringBuilder().apply {
+ append(context.getString(R.string.transition_finished))
+ setSpan(StyleSpan(Typeface.BOLD), 0, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+ append("\n${transition.from.chapter.name}\n\n")
+ val currSize = length
+ append(context.getString(R.string.transition_next))
+ setSpan(StyleSpan(Typeface.BOLD), currSize, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+ append("\n${nextChapter.chapter.name}\n\n")
+ }
+ } else {
+ context.getString(R.string.transition_no_next)
+ }
+
+ if (nextChapter != null) {
+ observeStatus(nextChapter, transition)
+ }
+ }
+
+ /**
+ * Binds a previous chapter transition on this view and subscribes to the page load status.
+ */
+ private fun bindPrevChapterTransition(transition: ChapterTransition.Prev) {
+ val prevChapter = transition.to
+
+ textView.text = if (prevChapter != null) {
+ SpannableStringBuilder().apply {
+ append(context.getString(R.string.transition_current))
+ setSpan(StyleSpan(Typeface.BOLD), 0, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+ append("\n${transition.from.chapter.name}\n\n")
+ val currSize = length
+ append(context.getString(R.string.transition_previous))
+ setSpan(StyleSpan(Typeface.BOLD), currSize, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+ append("\n${prevChapter.chapter.name}\n\n")
+ }
+ } else {
+ context.getString(R.string.transition_no_previous)
+ }
+
+ if (prevChapter != null) {
+ observeStatus(prevChapter, transition)
+ }
+ }
+
+ /**
+ * Observes the status of the page list of the next/previous chapter. Whenever there's a new
+ * state, the pages container is cleaned up before setting the new state.
+ */
+ private fun observeStatus(chapter: ReaderChapter, transition: ChapterTransition) {
+ unsubscribeStatus()
+
+ statusSubscription = chapter.stateObserver
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe { state ->
+ pagesContainer.removeAllViews()
+ when (state) {
+ is ReaderChapter.State.Wait -> {}
+ is ReaderChapter.State.Loading -> setLoading()
+ is ReaderChapter.State.Error -> setError(state.error, transition)
+ is ReaderChapter.State.Loaded -> setLoaded()
+ }
+ pagesContainer.visibleIf { pagesContainer.childCount > 0 }
+ }
+
+ addSubscription(statusSubscription)
+ }
+
+ /**
+ * Unsubscribes from the status subscription.
+ */
+ private fun unsubscribeStatus() {
+ removeSubscription(statusSubscription)
+ statusSubscription = null
+ }
+
+ /**
+ * Sets the loading state on the pages container.
+ */
+ private fun setLoading() {
+ val progress = ProgressBar(context, null, android.R.attr.progressBarStyle)
+
+ val textView = AppCompatTextView(context).apply {
+ wrapContent()
+ setText(R.string.transition_pages_loading)
+ }
+
+ pagesContainer.addView(progress)
+ pagesContainer.addView(textView)
+ }
+
+ /**
+ * Sets the loaded state on the pages container.
+ */
+ private fun setLoaded() {
+ // No additional view is added
+ }
+
+ /**
+ * Sets the error state on the pages container.
+ */
+ private fun setError(error: Throwable, transition: ChapterTransition) {
+ val textView = AppCompatTextView(context).apply {
+ wrapContent()
+ text = context.getString(R.string.transition_pages_error, error.message)
+ }
+
+ val retryBtn = AppCompatButton(context).apply {
+ wrapContent()
+ setText(R.string.action_retry)
+ setOnClickListener {
+ val toChapter = transition.to
+ if (toChapter != null) {
+ viewer.activity.requestPreloadChapter(toChapter)
+ }
+ }
+ }
+
+ pagesContainer.addView(textView)
+ pagesContainer.addView(retryBtn)
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt
new file mode 100644
index 000000000..6adee83c2
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt
@@ -0,0 +1,251 @@
+package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
+
+import android.support.v7.widget.RecyclerView
+import android.support.v7.widget.WebtoonLayoutManager
+import android.view.KeyEvent
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import eu.kanade.tachiyomi.ui.reader.ReaderActivity
+import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
+import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
+import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
+import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
+import rx.subscriptions.CompositeSubscription
+import timber.log.Timber
+
+/**
+ * Implementation of a [BaseViewer] to display pages with a [RecyclerView].
+ */
+class WebtoonViewer(val activity: ReaderActivity) : BaseViewer {
+
+ /**
+ * Recycler view used by this viewer.
+ */
+ val recycler = WebtoonRecyclerView(activity)
+
+ /**
+ * Frame containing the recycler view.
+ */
+ private val frame = WebtoonFrame(activity)
+
+ /**
+ * Layout manager of the recycler view.
+ */
+ private val layoutManager = WebtoonLayoutManager(activity)
+
+ /**
+ * Adapter of the recycler view.
+ */
+ private val adapter = WebtoonAdapter(this)
+
+ /**
+ * Distance to scroll when the user taps on one side of the recycler view.
+ */
+ private var scrollDistance = activity.resources.displayMetrics.heightPixels * 3 / 4
+
+ /**
+ * Currently active item. It can be a chapter page or a chapter transition.
+ */
+ private var currentPage: Any? = null
+
+ /**
+ * Configuration used by this viewer, like allow taps, or crop image borders.
+ */
+ val config = WebtoonConfig()
+
+ /**
+ * Subscriptions to keep while this viewer is used.
+ */
+ val subscriptions = CompositeSubscription()
+
+ init {
+ recycler.visibility = View.GONE // Don't let the recycler layout yet
+ recycler.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ recycler.itemAnimator = null
+ recycler.layoutManager = layoutManager
+ recycler.adapter = adapter
+ recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+ override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
+ val position = layoutManager.findLastEndVisibleItemPosition()
+ val item = adapter.items.getOrNull(position)
+ if (item != null && currentPage != item) {
+ currentPage = item
+ when (item) {
+ is ReaderPage -> onPageSelected(item, position)
+ is ChapterTransition -> onTransitionSelected(item)
+ }
+ }
+
+ if (dy < 0) {
+ val firstIndex = layoutManager.findFirstVisibleItemPosition()
+ val firstItem = adapter.items.getOrNull(firstIndex)
+ if (firstItem is ChapterTransition.Prev && firstItem.to != null) {
+ activity.requestPreloadChapter(firstItem.to)
+ }
+ }
+ }
+ })
+ recycler.tapListener = { event ->
+ val positionX = event.rawX
+ when {
+ positionX < recycler.width * 0.33 -> if (config.tappingEnabled) scrollUp()
+ positionX > recycler.width * 0.66 -> if (config.tappingEnabled) scrollDown()
+ else -> activity.toggleMenu()
+ }
+ }
+ recycler.longTapListener = f@ { event ->
+ if (activity.menuVisible || config.longTapEnabled) {
+ val child = recycler.findChildViewUnder(event.x, event.y)
+ val position = recycler.getChildAdapterPosition(child)
+ val item = adapter.items.getOrNull(position)
+ if (item is ReaderPage) {
+ activity.onPageLongTap(item)
+ return@f true
+ }
+ }
+ false
+ }
+
+ frame.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ frame.addView(recycler)
+ }
+
+ /**
+ * Returns the view this viewer uses.
+ */
+ override fun getView(): View {
+ return frame
+ }
+
+ /**
+ * Destroys this viewer. Called when leaving the reader or swapping viewers.
+ */
+ override fun destroy() {
+ super.destroy()
+ config.unsubscribe()
+ subscriptions.unsubscribe()
+ }
+
+ /**
+ * Called from the RecyclerView listener when a [page] is marked as active. It notifies the
+ * activity of the change and requests the preload of the next chapter if this is the last page.
+ */
+ private fun onPageSelected(page: ReaderPage, position: Int) {
+ val pages = page.chapter.pages!! // Won't be null because it's the loaded chapter
+ Timber.d("onPageSelected: ${page.number}/${pages.size}")
+ activity.onPageSelected(page)
+
+ if (page === pages.last()) {
+ Timber.d("Request preload next chapter because we're at the last page")
+ val transition = adapter.items.getOrNull(position + 1) as? ChapterTransition.Next
+ if (transition?.to != null) {
+ activity.requestPreloadChapter(transition.to)
+ }
+ }
+ }
+
+ /**
+ * Called from the RecyclerView listener when a [transition] is marked as active. It request the
+ * preload of the destination chapter of the transition.
+ */
+ private fun onTransitionSelected(transition: ChapterTransition) {
+ Timber.d("onTransitionSelected: $transition")
+ val toChapter = transition.to
+ if (toChapter != null) {
+ Timber.d("Request preload destination chapter because we're on the transition")
+ activity.requestPreloadChapter(toChapter)
+ } else if (transition is ChapterTransition.Next) {
+ // No more chapters, show menu because the user is probably going to close the reader
+ activity.showMenu()
+ }
+ }
+
+ /**
+ * Tells this viewer to set the given [chapters] as active.
+ */
+ override fun setChapters(chapters: ViewerChapters) {
+ Timber.d("setChapters")
+ adapter.setChapters(chapters)
+
+ if (recycler.visibility == View.GONE) {
+ Timber.d("Recycler first layout")
+ val pages = chapters.currChapter.pages ?: return
+ moveToPage(pages[chapters.currChapter.requestedPage])
+ recycler.visibility = View.VISIBLE
+ }
+ }
+
+ /**
+ * Tells this viewer to move to the given [page].
+ */
+ override fun moveToPage(page: ReaderPage) {
+ Timber.d("moveToPage")
+ val position = adapter.items.indexOf(page)
+ if (position != -1) {
+ recycler.scrollToPosition(position)
+ } else {
+ Timber.d("Page $page not found in adapter")
+ }
+ }
+
+ /**
+ * Scrolls up by [scrollDistance].
+ */
+ private fun scrollUp() {
+ recycler.smoothScrollBy(0, -scrollDistance)
+ }
+
+ /**
+ * Scrolls down by [scrollDistance].
+ */
+ private fun scrollDown() {
+ recycler.smoothScrollBy(0, scrollDistance)
+ }
+
+ /**
+ * Called from the containing activity when a key [event] is received. It should return true
+ * if the event was handled, false otherwise.
+ */
+ override fun handleKeyEvent(event: KeyEvent): Boolean {
+ val isUp = event.action == KeyEvent.ACTION_UP
+
+ when (event.keyCode) {
+ KeyEvent.KEYCODE_VOLUME_DOWN -> {
+ if (!config.volumeKeysEnabled || activity.menuVisible) {
+ return false
+ } else if (isUp) {
+ if (!config.volumeKeysInverted) scrollDown() else scrollUp()
+ }
+ }
+ KeyEvent.KEYCODE_VOLUME_UP -> {
+ if (!config.volumeKeysEnabled || activity.menuVisible) {
+ return false
+ } else if (isUp) {
+ if (!config.volumeKeysInverted) scrollUp() else scrollDown()
+ }
+ }
+ KeyEvent.KEYCODE_MENU -> if (isUp) activity.toggleMenu()
+
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.KEYCODE_PAGE_UP -> if (isUp) scrollUp()
+
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_PAGE_DOWN -> if (isUp) scrollDown()
+ else -> return false
+ }
+ return true
+ }
+
+ /**
+ * Called from the containing activity when a generic motion [event] is received. It should
+ * return true if the event was handled, false otherwise.
+ */
+ override fun handleGenericMotionEvent(event: MotionEvent): Boolean {
+ return false
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt
index 13dce8177..bcf418761 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt
@@ -165,9 +165,8 @@ class RecentChaptersPresenter(
* @param chapters list of chapters
*/
fun deleteChapters(chapters: List) {
- Observable.from(chapters)
- .doOnNext { deleteChapter(it) }
- .toList()
+ Observable.just(chapters)
+ .doOnNext { deleteChaptersInternal(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ ->
@@ -184,16 +183,23 @@ class RecentChaptersPresenter(
}
/**
- * Delete selected chapter
+ * Delete selected chapters
*
- * @param item chapter that is selected
+ * @param items chapters selected
*/
- private fun deleteChapter(item: RecentChapterItem) {
- val source = sourceManager.get(item.manga.source) ?: return
- downloadManager.queue.remove(item.chapter)
- downloadManager.deleteChapter(item.chapter, item.manga, source)
- item.status = Download.NOT_DOWNLOADED
- item.download = null
+ private fun deleteChaptersInternal(chapterItems: List) {
+ val itemsByManga = chapterItems.groupBy { it.manga.id }
+ for ((_, items) in itemsByManga) {
+ val manga = items.first().manga
+ val source = sourceManager.get(manga.source) ?: continue
+ val chapters = items.map { it.chapter }
+
+ downloadManager.deleteChapters(chapters, manga, source)
+ items.forEach {
+ it.status = Download.NOT_DOWNLOADED
+ it.download = null
+ }
+ }
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt
index 690327c8c..78ea8d20f 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt
@@ -31,9 +31,9 @@ class SettingsGeneralController : SettingsController() {
listPreference {
key = Keys.lang
titleRes = R.string.pref_language
- entryValues = arrayOf("", "ar", "bg", "bn", "de", "en-US", "en-GB", "es", "fr", "hi",
- "hu", "in", "it", "ja", "ko", "lv", "ms", "nl", "pl", "pt", "pt-BR", "ro",
- "ru", "vi")
+ entryValues = arrayOf("", "ar", "bg", "bn", "ca", "cs", "de", "el", "en-US", "en-GB",
+ "es", "fr", "hi", "hu", "in", "it", "ja", "ko", "lv", "ms", "nb-rNO", "nl", "pl", "pt",
+ "pt-BR", "ro", "ru", "sr", "sv", "th", "tr", "uk", "vi", "zh-rCN")
entries = entryValues.map { value ->
val locale = LocaleHelper.getLocaleFromString(value.toString())
locale?.getDisplayName(locale)?.capitalize() ?:
@@ -54,8 +54,9 @@ class SettingsGeneralController : SettingsController() {
intListPreference {
key = Keys.theme
titleRes = R.string.pref_theme
- entriesRes = arrayOf(R.string.light_theme, R.string.dark_theme, R.string.amoled_theme)
- entryValues = arrayOf("1", "2", "3")
+ entriesRes = arrayOf(R.string.light_theme, R.string.dark_theme,
+ R.string.amoled_theme, R.string.darkblue_theme)
+ entryValues = arrayOf("1", "2", "3", "4")
defaultValue = "1"
summary = "%s"
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt
index 40dc13323..ab9b32fc1 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt
@@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.setting
+import android.os.Build
import android.support.v7.preference.PreferenceScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.SharedData.map
@@ -55,14 +56,6 @@ class SettingsReaderController : SettingsController() {
defaultValue = "0"
summary = "%s"
}
- intListPreference {
- key = Keys.imageDecoder
- titleRes = R.string.pref_image_decoder
- entries = arrayOf("Image", "Rapid", "Skia")
- entryValues = arrayOf("0", "1", "2")
- defaultValue = "0"
- summary = "%s"
- }
intListPreference {
key = Keys.doubleTapAnimationSpeed
titleRes = R.string.pref_double_tap_anim_speed
@@ -86,6 +79,13 @@ class SettingsReaderController : SettingsController() {
titleRes = R.string.pref_show_page_number
defaultValue = true
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ switchPreference {
+ key = Keys.trueColor
+ titleRes = R.string.pref_true_color
+ defaultValue = false
+ }
+ }
intListPreference {
key = Keys.eh_readerThreads
title = "Download threads"
@@ -178,6 +178,11 @@ class SettingsReaderController : SettingsController() {
titleRes = R.string.pref_read_with_tapping
defaultValue = true
}
+ switchPreference {
+ key = Keys.readWithLongTap
+ titleRes = R.string.pref_read_with_long_tap
+ defaultValue = true
+ }
switchPreference {
key = Keys.readWithVolumeKeys
titleRes = R.string.pref_read_with_volume_keys
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt
index 0f7251bbf..699c253d2 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt
@@ -27,13 +27,6 @@ class SettingsTrackingController : SettingsController(),
titleRes = R.string.pref_auto_update_manga_sync
defaultValue = true
}
- switchPreference {
- key = Keys.askUpdateTrack
- titleRes = R.string.pref_ask_update_manga_sync
- defaultValue = false
- }.apply {
- dependency = Keys.autoUpdateTrack // the preference needs to be attached.
- }
preferenceCategory {
titleRes = R.string.services
@@ -88,4 +81,4 @@ class SettingsTrackingController : SettingsController(),
updatePreference(service.id)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt
index 18799f178..60aff194a 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt
@@ -51,7 +51,12 @@ fun syncChaptersWithSource(db: DatabaseHelper,
toAdd.add(sourceChapter)
} else {
//this forces metadata update for the main viewable things in the chapter list
+ if (source is HttpSource) {
+ source.prepareNewChapter(sourceChapter, manga)
+ }
+
ChapterRecognition.parseChapterNumber(sourceChapter, manga)
+
if (shouldUpdateDbChapter(dbChapter, sourceChapter)) {
dbChapter.scanlator = sourceChapter.scanlator
dbChapter.name = sourceChapter.name
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt
index 24b6b6cb4..2433a1199 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt
@@ -13,6 +13,7 @@ import android.os.Build
import android.os.PowerManager
import android.os.VibrationEffect
import android.os.Vibrator
+import android.support.annotation.AttrRes
import android.support.annotation.StringRes
import android.support.v4.app.NotificationCompat
import android.support.v4.content.ContextCompat
@@ -83,7 +84,7 @@ fun Context.hasPermission(permission: String)
*
* @param resource the attribute.
*/
-fun Context.getResourceColor(@StringRes resource: Int): Int {
+fun Context.getResourceColor(@AttrRes resource: Int): Int {
val typedArray = obtainStyledAttributes(intArrayOf(resource))
val attrValue = typedArray.getColor(0, 0)
typedArray.recycle()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt
index eb90b38a1..edff38614 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt
@@ -8,47 +8,9 @@ import android.os.Environment
import android.support.v4.content.ContextCompat
import android.support.v4.os.EnvironmentCompat
import java.io.File
-import java.io.InputStream
-import java.net.URLConnection
object DiskUtil {
- fun isImage(name: String, openStream: (() -> InputStream)? = null): Boolean {
- val contentType = try {
- URLConnection.guessContentTypeFromName(name)
- } catch (e: Exception) {
- null
- } ?: openStream?.let { findImageMime(it) }
-
- return contentType?.startsWith("image/") ?: false
- }
-
- fun findImageMime(openStream: () -> InputStream): String? {
- try {
- openStream().buffered().use {
- val bytes = ByteArray(8)
- it.mark(bytes.size)
- val length = it.read(bytes, 0, bytes.size)
- it.reset()
- if (length == -1)
- return null
- if (bytes[0] == 'G'.toByte() && bytes[1] == 'I'.toByte() && bytes[2] == 'F'.toByte() && bytes[3] == '8'.toByte()) {
- return "image/gif"
- } else if (bytes[0] == 0x89.toByte() && bytes[1] == 0x50.toByte() && bytes[2] == 0x4E.toByte()
- && bytes[3] == 0x47.toByte() && bytes[4] == 0x0D.toByte() && bytes[5] == 0x0A.toByte()
- && bytes[6] == 0x1A.toByte() && bytes[7] == 0x0A.toByte()) {
- return "image/png"
- } else if (bytes[0] == 0xFF.toByte() && bytes[1] == 0xD8.toByte() && bytes[2] == 0xFF.toByte()) {
- return "image/jpeg"
- } else if (bytes[0] == 'W'.toByte() && bytes[1] == 'E'.toByte() && bytes[2] == 'B'.toByte() && bytes[3] == 'P'.toByte()) {
- return "image/webp"
- }
- }
- } catch(e: Exception) {
- }
- return null
- }
-
fun hashKeyForDisk(key: String): String {
return Hash.md5(key)
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/EpubFile.kt b/app/src/main/java/eu/kanade/tachiyomi/util/EpubFile.kt
new file mode 100644
index 000000000..f1c81b5b8
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/EpubFile.kt
@@ -0,0 +1,117 @@
+package eu.kanade.tachiyomi.util
+
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import java.io.Closeable
+import java.io.File
+import java.io.InputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+
+/**
+ * Wrapper over ZipFile to load files in epub format.
+ */
+class EpubFile(file: File) : Closeable {
+
+ /**
+ * Zip file of this epub.
+ */
+ private val zip = ZipFile(file)
+
+ /**
+ * Closes the underlying zip file.
+ */
+ override fun close() {
+ zip.close()
+ }
+
+ /**
+ * Returns an input stream for reading the contents of the specified zip file entry.
+ */
+ fun getInputStream(entry: ZipEntry): InputStream {
+ return zip.getInputStream(entry)
+ }
+
+ /**
+ * Returns the zip file entry for the specified name, or null if not found.
+ */
+ fun getEntry(name: String): ZipEntry? {
+ return zip.getEntry(name)
+ }
+
+ /**
+ * Returns the path of all the images found in the epub file.
+ */
+ fun getImagesFromPages(): List {
+ val allEntries = zip.entries().toList()
+ val ref = getPackageHref()
+ val doc = getPackageDocument(ref)
+ val pages = getPagesFromDocument(doc)
+ val hrefs = getHrefMap(ref, allEntries.map { it.name })
+ return getImagesFromPages(pages, hrefs)
+ }
+
+ /**
+ * Returns the path to the package document.
+ */
+ private fun getPackageHref(): String {
+ val meta = zip.getEntry("META-INF/container.xml")
+ if (meta != null) {
+ val metaDoc = zip.getInputStream(meta).use { Jsoup.parse(it, null, "") }
+ val path = metaDoc.getElementsByTag("rootfile").first()?.attr("full-path")
+ if (path != null) {
+ return path
+ }
+ }
+ return "OEBPS/content.opf"
+ }
+
+ /**
+ * Returns the package document where all the files are listed.
+ */
+ private fun getPackageDocument(ref: String): Document {
+ val entry = zip.getEntry(ref)
+ return zip.getInputStream(entry).use { Jsoup.parse(it, null, "") }
+ }
+
+ /**
+ * Returns all the pages from the epub.
+ */
+ private fun getPagesFromDocument(document: Document): List {
+ val pages = document.select("manifest > item")
+ .filter { "application/xhtml+xml" == it.attr("media-type") }
+ .associateBy { it.attr("id") }
+
+ val spine = document.select("spine > itemref").map { it.attr("idref") }
+ return spine.mapNotNull { pages[it] }.map { it.attr("href") }
+ }
+
+ /**
+ * Returns all the images contained in every page from the epub.
+ */
+ private fun getImagesFromPages(pages: List, hrefs: Map): List {
+ return pages.map { page ->
+ val entry = zip.getEntry(hrefs[page])
+ val document = zip.getInputStream(entry).use { Jsoup.parse(it, null, "") }
+ document.getElementsByTag("img").mapNotNull { hrefs[it.attr("src")] }
+ }.flatten()
+ }
+
+ /**
+ * Returns a map with a relative url as key and abolute url as path.
+ */
+ private fun getHrefMap(packageHref: String, entries: List): Map {
+ val lastSlashPos = packageHref.lastIndexOf('/')
+ if (lastSlashPos < 0) {
+ return entries.associateBy { it }
+ }
+ return entries.associateBy { entry ->
+ if (entry.isNotBlank() && entry.length > lastSlashPos) {
+ entry.substring(lastSlashPos + 1)
+ } else {
+ entry
+ }
+ }
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ImageUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ImageUtil.kt
new file mode 100644
index 000000000..fa879d4b4
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/ImageUtil.kt
@@ -0,0 +1,74 @@
+package eu.kanade.tachiyomi.util
+
+import java.io.InputStream
+import java.net.URLConnection
+
+object ImageUtil {
+
+ fun isImage(name: String, openStream: (() -> InputStream)? = null): Boolean {
+ val contentType = try {
+ URLConnection.guessContentTypeFromName(name)
+ } catch (e: Exception) {
+ null
+ } ?: openStream?.let { findImageType(it)?.mime }
+ return contentType?.startsWith("image/") ?: false
+ }
+
+ fun findImageType(openStream: () -> InputStream): ImageType? {
+ return openStream().use { findImageType(it) }
+ }
+
+ fun findImageType(stream: InputStream): ImageType? {
+ try {
+ val bytes = ByteArray(8)
+
+ val length = if (stream.markSupported()) {
+ stream.mark(bytes.size)
+ stream.read(bytes, 0, bytes.size).also { stream.reset() }
+ } else {
+ stream.read(bytes, 0, bytes.size)
+ }
+
+ if (length == -1)
+ return null
+
+ if (bytes.compareWith(charByteArrayOf(0xFF, 0xD8, 0xFF))) {
+ return ImageType.JPG
+ }
+ if (bytes.compareWith(charByteArrayOf(0x89, 0x50, 0x4E, 0x47))) {
+ return ImageType.PNG
+ }
+ if (bytes.compareWith("GIF8".toByteArray())) {
+ return ImageType.GIF
+ }
+ if (bytes.compareWith("RIFF".toByteArray())) {
+ return ImageType.WEBP
+ }
+ } catch(e: Exception) {
+ }
+ return null
+ }
+
+ private fun ByteArray.compareWith(magic: ByteArray): Boolean {
+ for (i in 0 until magic.size) {
+ if (this[i] != magic[i]) return false
+ }
+ return true
+ }
+
+ private fun charByteArrayOf(vararg bytes: Int): ByteArray {
+ return ByteArray(bytes.size).apply {
+ for (i in 0 until bytes.size) {
+ set(i, bytes[i].toByte())
+ }
+ }
+ }
+
+ enum class ImageType(val mime: String, val extension: String) {
+ JPG("image/jpeg", "jpg"),
+ PNG("image/png", "png"),
+ GIF("image/gif", "gif"),
+ WEBP("image/webp", "webp")
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/RarContentProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/util/RarContentProvider.kt
deleted file mode 100755
index 12ad9706f..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/util/RarContentProvider.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package eu.kanade.tachiyomi.util
-
-import android.content.ContentProvider
-import android.content.ContentValues
-import android.content.res.AssetFileDescriptor
-import android.database.Cursor
-import android.net.Uri
-import android.os.ParcelFileDescriptor
-import eu.kanade.tachiyomi.BuildConfig
-import junrar.Archive
-import java.io.File
-import java.io.IOException
-import java.net.URLConnection
-import java.util.concurrent.Executors
-
-class RarContentProvider : ContentProvider() {
-
- private val pool by lazy { Executors.newCachedThreadPool() }
-
- companion object {
- const val PROVIDER = "${BuildConfig.APPLICATION_ID}.rar-provider"
- }
-
- override fun onCreate(): Boolean {
- return true
- }
-
- override fun getType(uri: Uri): String? {
- return URLConnection.guessContentTypeFromName(uri.toString())
- }
-
- override fun openAssetFile(uri: Uri, mode: String): AssetFileDescriptor? {
- try {
- val pipe = ParcelFileDescriptor.createPipe()
- pool.execute {
- try {
- val (rar, file) = uri.toString()
- .substringAfter("content://$PROVIDER")
- .split("!-/", limit = 2)
-
- Archive(File(rar)).use { archive ->
- val fileHeader = archive.fileHeaders.first { it.fileNameString == file }
-
- ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]).use { output ->
- archive.extractFile(fileHeader, output)
- }
- }
- } catch (e: Exception) {
- // Ignore
- }
- }
- return AssetFileDescriptor(pipe[0], 0, -1)
- } catch (e: IOException) {
- return null
- }
- }
-
- override fun query(p0: Uri?, p1: Array?, p2: String?, p3: Array?, p4: String?): Cursor? {
- return null
- }
-
- override fun insert(p0: Uri?, p1: ContentValues?): Uri {
- throw UnsupportedOperationException("not implemented")
- }
-
- override fun update(p0: Uri?, p1: ContentValues?, p2: String?, p3: Array?): Int {
- throw UnsupportedOperationException("not implemented")
- }
-
- override fun delete(p0: Uri?, p1: String?, p2: Array?): Int {
- throw UnsupportedOperationException("not implemented")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/RxExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/RxExtensions.kt
index 1f4933552..8a0c965ff 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/util/RxExtensions.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/RxExtensions.kt
@@ -10,4 +10,8 @@ operator fun CompositeSubscription.plusAssign(subscription: Subscription) = add(
fun Observable.combineLatest(o2: Observable, combineFn: (T, U) -> R): Observable {
return Observable.combineLatest(this, o2, combineFn)
-}
\ No newline at end of file
+}
+
+fun Subscription.addTo(subscriptions: CompositeSubscription) {
+ subscriptions.add(this)
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ZipContentProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ZipContentProvider.kt
deleted file mode 100755
index 737007720..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/util/ZipContentProvider.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package eu.kanade.tachiyomi.util
-
-import android.content.ContentProvider
-import android.content.ContentValues
-import android.content.res.AssetFileDescriptor
-import android.database.Cursor
-import android.net.Uri
-import android.os.ParcelFileDescriptor
-import eu.kanade.tachiyomi.BuildConfig
-import java.io.IOException
-import java.net.URL
-import java.net.URLConnection
-import java.util.concurrent.Executors
-
-class ZipContentProvider : ContentProvider() {
-
- private val pool by lazy { Executors.newCachedThreadPool() }
-
- companion object {
- const val PROVIDER = "${BuildConfig.APPLICATION_ID}.zip-provider"
- }
-
- override fun onCreate(): Boolean {
- return true
- }
-
- override fun getType(uri: Uri): String? {
- return URLConnection.guessContentTypeFromName(uri.toString())
- }
-
- override fun openAssetFile(uri: Uri, mode: String): AssetFileDescriptor? {
- try {
- val url = "jar:file://" + uri.toString().substringAfter("content://$PROVIDER")
- val input = URL(url).openStream()
- val pipe = ParcelFileDescriptor.createPipe()
- pool.execute {
- try {
- val output = ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])
- input.use {
- output.use {
- input.copyTo(output)
- }
- }
- } catch (e: IOException) {
- // Ignore
- }
- }
- return AssetFileDescriptor(pipe[0], 0, -1)
- } catch (e: IOException) {
- return null
- }
- }
-
- override fun query(p0: Uri?, p1: Array?, p2: String?, p3: Array?, p4: String?): Cursor? {
- return null
- }
-
- override fun insert(p0: Uri?, p1: ContentValues?): Uri {
- throw UnsupportedOperationException("not implemented")
- }
-
- override fun update(p0: Uri?, p1: ContentValues?, p2: String?, p3: Array?): Int {
- throw UnsupportedOperationException("not implemented")
- }
-
- override fun delete(p0: Uri?, p1: String?, p2: Array?): Int {
- throw UnsupportedOperationException("not implemented")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt
index c2c21a66b..3effd64ec 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt
@@ -27,4 +27,8 @@ abstract class ViewPagerAdapter : PagerAdapter() {
return view === obj
}
-}
\ No newline at end of file
+ interface PositionableView {
+ val item: Any
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt
index f9fc355c0..500a9225b 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt
@@ -46,7 +46,6 @@ class TrackLoginDialog(bundle: Bundle? = null) : LoginDialogPreference(bundle) {
login.setText(R.string.unknown_error)
error.message?.let { context.toast(it) }
})
-
}
}
diff --git a/app/src/main/res/drawable/ic_image_black_24dp.xml b/app/src/main/res/drawable/ic_image_black_24dp.xml
new file mode 100644
index 000000000..b2018595e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_image_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout-land/reader_color_filter_sheet.xml b/app/src/main/res/layout-land/reader_color_filter_sheet.xml
new file mode 100644
index 000000000..ba4d45e40
--- /dev/null
+++ b/app/src/main/res/layout-land/reader_color_filter_sheet.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/reader_activity.xml b/app/src/main/res/layout/reader_activity.xml
index 51d23ad6e..0dd6c47dd 100755
--- a/app/src/main/res/layout/reader_activity.xml
+++ b/app/src/main/res/layout/reader_activity.xml
@@ -11,17 +11,18 @@
android:layout_height="match_parent">
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ tools:visibility="visible"/>
@@ -54,7 +56,8 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
- android:theme="?attr/actionBarTheme" />
+ android:background="?colorPrimary"
+ android:elevation="4dp" />
+ android:textSize="15sp"
+ android:clickable="true"
+ tools:text="1"/>
-
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+ android:textSize="15sp"
+ android:clickable="true"
+ tools:text="15"/>
@@ -206,4 +212,4 @@
android:layout_height="match_parent"
android:visibility="gone"/>
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/reader_color_filter.xml b/app/src/main/res/layout/reader_color_filter.xml
new file mode 100644
index 000000000..736ee590e
--- /dev/null
+++ b/app/src/main/res/layout/reader_color_filter.xml
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/reader_color_filter_sheet.xml b/app/src/main/res/layout/reader_color_filter_sheet.xml
new file mode 100644
index 000000000..618a8a77f
--- /dev/null
+++ b/app/src/main/res/layout/reader_color_filter_sheet.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/reader_custom_filter_dialog.xml b/app/src/main/res/layout/reader_custom_filter_dialog.xml
deleted file mode 100755
index 0f5483dea..000000000
--- a/app/src/main/res/layout/reader_custom_filter_dialog.xml
+++ /dev/null
@@ -1,263 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/reader_page_decode_error.xml b/app/src/main/res/layout/reader_page_decode_error.xml
deleted file mode 100755
index e623788c5..000000000
--- a/app/src/main/res/layout/reader_page_decode_error.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/reader_page_sheet.xml b/app/src/main/res/layout/reader_page_sheet.xml
new file mode 100644
index 000000000..446f00d61
--- /dev/null
+++ b/app/src/main/res/layout/reader_page_sheet.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/reader_pager_item.xml b/app/src/main/res/layout/reader_pager_item.xml
deleted file mode 100755
index f11dea912..000000000
--- a/app/src/main/res/layout/reader_pager_item.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/reader_settings_dialog.xml b/app/src/main/res/layout/reader_settings_dialog.xml
deleted file mode 100755
index ea9793236..000000000
--- a/app/src/main/res/layout/reader_settings_dialog.xml
+++ /dev/null
@@ -1,186 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/reader_settings_sheet.xml b/app/src/main/res/layout/reader_settings_sheet.xml
new file mode 100644
index 000000000..d28155d70
--- /dev/null
+++ b/app/src/main/res/layout/reader_settings_sheet.xml
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/reader_webtoon_item.xml b/app/src/main/res/layout/reader_webtoon_item.xml
deleted file mode 100755
index 9bb7826b7..000000000
--- a/app/src/main/res/layout/reader_webtoon_item.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/raw/changelog_release.xml b/app/src/main/res/raw/changelog_release.xml
index 4d78120b7..289f9bf48 100755
--- a/app/src/main/res/raw/changelog_release.xml
+++ b/app/src/main/res/raw/changelog_release.xml
@@ -1,5 +1,32 @@
+
+ Updated Cloudflare.
+ Fixed crashes with some translations.
+
+
+
+ Fixed latest Cloudflare changes.
+ Bundled SQLite for better performance and new features.
+ Restored dark blue theme.
+ Added a MAL API workaround.
+ Fixed search issues on Kitsu and AniList.
+ Fixed an issue where the image was centered when using the vertical reader.
+ Updated translations.
+
+
+
+ Added a new reader with many fixes.
+ Added GIF support.
+ Fixed loading errors with local compressed files.
+ Fixed an issue where images couldn't be shared.
+ Removed image decoders setting because it's not needed anymore.
+ Updated translations.
+ Show all entries in library even if their extensions are not installed
+ Fixed search issues on Kitsu and AniList.
+ Other minor bugfixes.
+
+
Add ability to customize reader cache size
Fix "Retry all" button not retrying all pages
@@ -31,27 +58,18 @@
Updated Anilist's API to v2.
-
Added Github link to about.
-
Fixed indonesian language not working.
-
Fixed an issue on KitKat that crashed the app when scheduling updates.
-
Fixed a few more issues introduced on the previous release.
Fixed the tracking search layout when there are many results.
-
Separate english language into american and british so that dates are formatted according to that locale.
-
Added Firebase analytics, for Android API distribution.
-
Crop borders for webtoons now has a separate setting.
-
The downloader now runs in a foreground service to prevent it from being killed.
-
Fixed a few weird crashes.
@@ -95,36 +113,25 @@
Fixed missing downloaded label in chapters screen.
-
Fixed updater in KitKat and lower due to TLS.
Updated Cloudflare bypass.
-
Enabled TLS 1.1 and TLS 1.2 on Android KitKat and lower.
-
Minor UI changes.
Added extensions support. You can now install and update extensions within the app.
If you installed any extension previously through F-Droid, you'll have to uninstall them first.
-
Added a custom download option to download N chapters.
-
Updated manga info layout, with clickable components to copy to clipboard or perform a global search.
-
Added an option to change the animation speed of a double tap in the reader.
-
Improved tracking results UI with covers.
-
Dropped support for simultaneous downloads.
-
Batoto is now a legacy source, you can only use it to migrate.
-
Updated dark theme and reader theme.
-
Bugfixes and minor UI/UX improvements.
@@ -140,72 +147,49 @@
[b]Notice to Batoto users.[/b] As you may already know, Batoto will cease to work in a few days.
We're working on a feature to help migrating the library to other sources and should be available shortly.
Please be patient.
-
Fixed http 503 errors due to Cloudflare changes.
-
Minor UI improvements.
Backups now properly restore tracking information.
-
Fixed library view and its overflow menu visible in other screens.
-
Fixed updater's notification in Android O.
-
Fixed a crash when rotating the screen in the chapters view.
-
Improved peformance of the app when using a custom downloads directory.
Added a download cache for faster navigation.
-
Enabled Cloudflare for Batoto.
-
Fixed some issues with automatic backups.
-
Fixed a bootloop issue with devices running Cyanogenmod 12 or 13.
Added a global search feature with a new catalogue screen.
-
Added an option to show downloaded chapters badges in the library.
-
Added an scrollbar in the chapter list.
-
Fixed some issues with bundled catalogues.
-
Changed the page indicator in the reader to support devices with rounded corners.
-
Crash fixes.
Fixed a crash when retrying a page.
-
Fixed a crash when sharing an image.
-
Fixed disappearing toolbar buttons in the catalog after a search.
Added a new completed manga filter for the library.
-
Added scanlator to chapters (if supported by source).
-
Added Discord server link.
-
Added new translations.
-
Extensions shouldn't crash the app anymore.
-
Crop borders is supported in webtoon reader and fixed in Android O.
-
Fixed a bug where storage permissions were always requested.
-
Minor UI and crash fixes.
@@ -233,64 +217,41 @@
Added sorting by total chapters.
-
Added an option to reverse volume keys navigation.
-
Added AMOLED theme.
-
Improved recent chapters view.
-
Improved UI with a single activity approach.
-
Fixed backup restore issues.
-
Fixed Kitsu http 400 errors.
-
Fixed Batoto catalogue.
New backup system. Smaller file size but requires a network connection to restore.
-
Fixed descriptions showing a single line.
-
Added Nougat shortcuts and round icon.
-
Added an option to add a manga to a specific category.
-
Improved new chapters notification.
-
Support Kitsu new rating system.
-
Last read page is now retained in webtoon reader.
Added an option to auto download from selected categories.
-
Handle a few more directories for local manga.
-
Update Kissmanga parser.
-
Fixed downloader errors with some manga titles.
-
Fixed gallery not showing saved images.
Support for local manga. Head to the [a href="https://github.com/inorichi/tachiyomi/wiki/Local-manga"]wiki page[/a] for instructions.
-
Added an option to detect and remove the white borders of the images.
-
Added advanced search for catalogues.
-
Russian, french, bulgarian and vietnamese translations.
-
Fixed a bug when changing chapters inside the reader with the buttons.
-
Fixed certain downloaded chapters not working with any decoder.
-
Fixed lost covers on some devices.
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 948a173fd..8294ff553 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -1,4 +1,4 @@
-
+
الاسم
اﻷقسام
المانجا
@@ -7,14 +7,14 @@
السجل
اﻹعدادات
- التحميلات المنتظرة
+ طابور التنزيل
مكتبتي
المقروءة حديثا
قائمة المانجا
تحديثات المكتبة
آخر التحديثات
الأقسام
- %1$d المحدد
+ حدّدت: %1$d
النسخ الاحتياطي
اﻹعدادات
@@ -49,7 +49,6 @@
تعديل صورة الغلاف
الترتيب لأسفل
الترتيب لأعلى
- الغير مقروءة
مكتملة التحميل
الغير مقروءة التالية
بدء
@@ -119,7 +118,6 @@
أثناء الشحن
تحديث المانجا المستمرة فقط
مزامنة الفصول بعد القراءة
- التأكيد قبل التحديث
سمة التطبيق
السمة الرئيسية
السمة الليلية
@@ -170,7 +168,6 @@
الوضع اﻷفقي اﻹجباري
الوضع الرأسي اﻹجباري
مسار التحميلات
- عدد التحميلات فى نفس الوقت
التحميل عبر الواي فاي فقط
الحذف عند التحديد كمقروءة
الحذف بعد إكمال القراءة
@@ -280,7 +277,6 @@
المستمرة
الغير معروف
المرخصة
- إضافة إلى المكتبة
إزالة من المكتبة
المؤلف
الفنان
@@ -354,8 +350,7 @@
الفصل %1$s
لم يتم العثور على الفصل التالي
لم يتم العثور على الفصل السابق
- لا يمكن تحميل الصورة.
-\nيمكنك تجربة تغيير إعدادت فك تشفير الصورة عن طريق الخيارات باﻷسفل
+ لا يمكن فكّ تشفير هذه الصورةِ
تحديث الفصل اﻷخير فى الخدمات المفعلة إلى %1$d؟
هل تريد تعيين هذه الصورة كغلاف؟
العارض لهذه السلسلة
@@ -397,7 +392,7 @@
لا توجد تحميلات
لا توجد فصول حديثة
لا توجد مانجا مقروءة مؤخرا
- المكتبة فارغة
+ المكتبة فارغة، يمكنك إضافة سلسلة إلى المكتبة الخاصة بك من القوائم.
مدير التحميل
خطأ
@@ -411,4 +406,76 @@
عام
المكتبة
مدير التحميل
-
+ترحيل المصدر
+ اﻹضافات
+ معلومات اﻹضافة
+
+
+ تحميل الشارات
+ الكل
+ التفاصيل
+ تحديث
+ تثبيت
+ المُعلقة
+ جارى التحميل
+ جاري التثبيت
+ تم التثبيت
+ الثقة
+ غير موثوق فيه
+ إلغاء التثبيت
+ التفضيلات
+ المتاحة
+ إضافة غير موثوق بها
+ هذه اﻹضافة موقعة بشهادة غير موثوق بها ولم يتم تفعيلها.
+\n
+\nيمكن لأي إضافة خبيثة قراءة بيانات اعتماد تسجيل الدخول المخزنة في تاتشييومي أو تنفيذ تعليمات برمجية عشوائية.
+\n
+\nبالثقة في هذه الشهادة يمكنك قبول هذه المخاطر.
+ الإصدار %1$s
+ اللغة: %1$s
+ لا توجد تفضيلات خاصة بهذه اﻹضافة لتعديلها
+
+ سرعة مؤثر النقر المزدوج
+ عارض الصفحات
+ لا مؤثرات
+ طبيعي
+ سريع
+ محلّي
+ فلاتر البحث
+ العنوان
+ تم الإضافة إلى المكتبة
+ تم الإزالة من المكتبة
+ تم التحديث
+ %1$s نُسخ إلى الحافظة
+ المصدر غير مثبت: %1$s
+
+ تحميل مقدار مخصص
+ مقدار
+ تحميل مخصص
+ إعادة القراءة
+ الحالة
+ تم البدء فيها
+ النوع
+ المؤلف
+ لم يتم تعيين رابط المانجت، برجاء الضغط على العنوان وتحديد مانجا مرة أخرى
+
+ اضغط لتحديد مصدر للترحيل منه
+ اختيار بيانات لتضمينها
+ تحديد
+ ترحيل
+ نسخ
+ الترحيل…
+
+ لا توجد لديك أي فئات. اضغط زر اﻹضافة لإنشاء واحد لتنظيم المكتبة الخاصة بك.
+
+ أزرق غامق
+ تمَّ الانتهاء من:
+ الحالي:
+ التّالي:
+ السّابق:
+ لا يوجدُ فصل تالي
+ لا يوجدُ فصل سابق
+ جار تحميل الصّفحات…
+ فشل تحميل الصّفحات: %1$s
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index 668a8005b..c42127a44 100755
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -1,11 +1,11 @@
-
+
Име
Настройки
Изтегляния
- Библиотека
+ Моята библиотека
Наскоро прочетени
Каталози
Наскоро обновени
@@ -16,23 +16,23 @@
Настройки
- Филтър
+ Филтрирай
Изтеглени
Отбелязани
Непрочетени
Прочетени
Премахни филтрирането
- По азбучен ред
- Последно прочетени
- По обновления
+ Азбучен ред
+ Последно четене
+ Последно обновление
Търсене
Избери всички
Отбележи като прочетена
Отбележи като непрочетена
- Отбележи предната като прочетена
+ Отбележи предните глави като прочетени
Изтегли
Отметни
- Премахни отметка
+ Премахни отметката
Изтрий
Обнови
Обнови библиотеката
@@ -41,29 +41,28 @@
Добави категория
Редактирай категории
Преименувай категория
- Премести в категория
- Промени корицата
+ Премести в категории
+ Смени корицата
Сортирай по възходящ ред
Сортирай по низходящ ред
- Непрочетени
Изтеглени
Следваща непрочетена
- Старт
- Стоп
- Пауза
+ Започни
+ Спри
+ Паузирай
Изчисти
- Предходна глава
+ Предишна глава
Следваща глава
Опитай пак
Премахни
Продължи
Отвори в браузър
- Пряк път на начален екран
- Начин на показване
+ Добави към началния си екран
+ Промяна на режима на показване
Показване
- Таблица
- Лист
- Филтрирай
+ Решетка
+ Списък
+ Сложи филтър
Отмени
Сортирай
Инсталирай
@@ -73,8 +72,8 @@
Върни
- Изтриване…
- Зареждане…
+ Изтрива се …
+ Зареждане …
@@ -98,19 +97,19 @@
На всеки 3 часа
На всеки 6 часа
На всеки 12 часа
- Всекидневно
- На всеки 2 дена
- Категории, включени в глобалното обновяване
+ Ежедневно
+ На всеки 2 дни
+ Категории за включване в
+\nглобално обновяване
Всички
Ограничения при обновяване
Обновявай само когато дадените условия са спазени
Wi-Fi
На зарядно устройство
- Обновявай само текуща манга
+ Обновявай само излизаща манга
Синхронизирай глави след прочитане
- Искане за потвърждение преди обновяване
Тема на приложението
- Основна тема
+ Главна тема
Тъмна тема
Начален екран
Език
@@ -162,7 +161,6 @@
Директория на изтеглянията
- Едновременно теглене
Тегли само с Wi-Fi
Изтрий, щом маркирам като прочетено
Изтривай след прочитане
@@ -187,8 +185,8 @@
Бисквитки изчистени
Рестартирай диалогичните избори
Изчисти базата данни
- Изтрий манга и глави, които не се намират в библиотеката ми
- Сигурни ли сте? Прочетените глави и напредъкът на мангата, която не се намира в библиотеката Ви, ще бъдат изгубени
+ Изтрий манга и глави, които не са в библиотеката ми
+ Сигурни ли сте\? Прочетените глави и напредъкът на манги, които не са в библиотеката Ви, ще бъдат изгубени
Базата данни изчистена
Обнови метаданните на библиотеката
Обновява кориците, жанровете, описанието и статуса на мангите
@@ -200,7 +198,7 @@
Автоматично проверявай за актуализации
Изпращай данни за грешки
- Помага за изправянето на бъгове. Лични данни няма да бъдат изпратени
+ Помага за оправянето на бъгове. Няма да се изпращат лични данни
@@ -233,7 +231,6 @@
Излизаща
Неизвестно
Лицензирана
- Добави към библиотеката
Премахни от библиотеката
Автор
Артист
@@ -278,7 +275,7 @@
Завършена
Изоставена
На заден план
- Планирам да чета
+ За четене
Оценка
Заглавие
Статус
@@ -293,7 +290,7 @@
Рестартирай всички глави за тази манга
- Да добавя ли мангата към библиотеката?
+ Добавяне на мангата към библиотеката\?
Изображението запазено
@@ -310,25 +307,24 @@
Страница: %1$d
Глава %1$s
Не беше намерена следваща глава
- Не беше намерена предходна глава
- Изображението не можа да бъде заредено.
-Опитайте да смените декодера на изображението или някоя от изброените опции
+ Не беше намерена предишна глава
+ Неуспешно декодиране на изображението
Обнови последната прочетена глава в %1$d?
Желаете ли да използвате това изображение за корица?
- Посока на четене за поредицата
+ зрител за тази серия
Резервно копие
- %1$s - Гл.%2$s
+ %1$s - Глава %2$s
Получи се грешка при изтеглянето на главите. Може да опитате пак в секцията на изтеглянията
Напредък на обновяването: %1$d/%2$d
- Открити нови глави
+ Нови глави са намерени
Грешка при обновяването на корицата
Моля, добавете мангата в библиотеката си, преди да направите това
Синхронизиране отменено
@@ -380,11 +376,11 @@
Следене
История
- Общо глави
+ Общ брой глави
Затвори
Премести
- Експорт
- Отвори лога
+ Експортирай
+ отворен дънер
Създай
Възстанови
@@ -399,12 +395,12 @@
Изрязвай границите
Обърнати бутони за звука
- Категории, включени в изтеглянето
+ Категории за включване при теглене
Създай резервно копие
Може да се използва за възстановяване на текущата библиотека
Възстанови резервно копие
- Възстанови предишна библиотека от резервно копие
+ Възстанови библиотеката от резервно копие
Директория за резервното копие
Обслужване
Честота на запазване
@@ -444,7 +440,7 @@
\nСъщо проверете дали сте влезли коректно в източниците, които го изискват, преди възстановяването.
Глобално търсене
Отвори
- Влизане
+ Вход
Други
Глобално търсене…
@@ -456,12 +452,12 @@
Общи
Библиотека
Изтегляния
-Показвай изтеглени глави
+Изтегляне на значки
Локална
- Нямате категории. Натиснете плюса, за да ги създадете и да организирате библиотеката си.
+ Нямате категории. Натиснете плюса, за да създадете такава и да организирате библиотеката си.
Смяна на източник
- Разширения
+ Добавки
Информация
@@ -509,4 +505,24 @@
Копирай
Мигриране…
-
+ пейджър
+ Източникът не е инсталиран: %1$s
+
+ Препрочитане
+ Статус
+ Започната на
+ Тип
+ Автор
+ Мангата няма връзка с акаунта, молим отново да натиснете заглавието и да изберете мангата
+
+ Завършени:
+ Текущи:
+ Следва:
+ Предишна:
+ Няма повече глави
+ Няма предишна глава
+ Зареждане на страниците…
+ "Неуспешно зареждане на страници: %1$s "
+
+ Тъмно син
+
\ No newline at end of file
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index e0a16bfc8..cbc91aef5 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -1,4 +1,4 @@
-
+
নাম
ধরণ
মাংগা
@@ -50,7 +50,6 @@
কভার ছবি সম্পাদন
সাজান
বাছাই করুন
- অপঠিত
ডাউনলোডেড
অপঠিত পরবর্তি
শুরু
@@ -123,7 +122,6 @@
চার্জ হচ্ছে
কেবল চলমান মাংগা আপডেট করুন
পড়ার পর অধ্যায়গুলো সুসংগত করুন
- আপডেটের আগে নিশ্চিত করুন
এপ্লিকেশন থিম
প্রধান থিম
কালো থিম
@@ -180,7 +178,6 @@
ডাউনলোডের নির্দেশক
- সমকালীন ডাউনলোডগুলো
শুধুমাত্র ওয়াই-ফাইয়ে ডাউনলোড করুন
পঠিত হলে সরিয়ে ফেলুন
পড়ার পর সরিয়ে ফেলুন
@@ -280,7 +277,6 @@
চলমান
অজানা
লাইসেন্সকৃত
- মাংগাশালায় যোগ করুন
মাংগাশালা থেকে সরিয়ে ফেলুন
লেখক
চিত্রকর
@@ -354,8 +350,7 @@
অধ্যায় %1$s
পরবর্তী অধ্যায় খুঁজে পাওয়া যায়নি
আগের অধ্যায় খুঁজে পাওয়া যায়নি
- ছবি লোড করা সম্ভব হয়নি।
-\nছবি ডিকোডার পরিবর্তন করুন অথবা নিচের যেকোন একটি অপশন চেষ্টা করুন
+ ছবি decoded করা যাবে না
শেষ পড়া অধ্যায় সেবাগুলোতে আপডেট করুন %1$d?
আপনি কি এই ছবিটিকে কভার হিসেবে সেট করতে চান?
এই সিরিজের দর্শক
@@ -461,4 +456,22 @@
কপি
সরানো হচ্ছে…
-
+ পেজার
+ উৎস ইন্সটল করা নেই: %1$s
+
+ পুনরায় পড়া
+ স্ট্যাটাস
+ শুরু হয়েছে
+ ধরণ
+ লেখক
+ গাঢ় নীল
+ "শেষ :"
+ "এখনকার :"
+ আসছে :
+ পূর্ববর্তী :
+ কোন পরের অধ্যায় আছে
+ কোন পূর্ববর্তী অধ্যায় আছে
+ পৃষ্ঠা লোড হচ্ছে …
+ পৃষ্ঠা লোড করতে ব্যর্থ হয়েছে: %1$s
+ মাংগা ইউআরএল সেট না শিরোনাম ক্লিক করুন এবং আবার মাংগা নির্বাচন করুন
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
new file mode 100644
index 000000000..6bafab1f9
--- /dev/null
+++ b/app/src/main/res/values-ca/strings.xml
@@ -0,0 +1,204 @@
+
+Nom
+ Categories
+ Manga
+ Capítols
+ Seguiment
+ Historial
+ Configuració
+ Cua de descàrregues
+ La meva biblioteca
+ Llegit recentment
+ Catàlegs
+ Actualitzacions de biblioteca
+ Últimes actualitzacions
+ Categories
+ Seleccionats: %1$d
+ Còpia de seguretat
+ Font de migració
+ Extensions
+ Informació d\'extensió
+ Configuració
+ Filtrar
+ Descarregat
+ Marcat
+ No llegit
+ Llegit
+ Eliminar filtres
+ Alfabèticament
+ Número de capítols
+ Últim llegit
+ Última actualització
+ Buscar
+ Cerca global
+ Seleccionar-ho tot
+ Marcar com llegit
+ Marcar com no llegit
+ Marcar anterior com llegits
+ Descarregar
+ Marcar
+ Desmarcar
+ Eliminar
+ Actualitzar
+ Actualitzar biblioteca
+ Editar
+ Afegir
+ Afegir categoria
+ Editar categories
+ Renombrar categoria
+ Moure a categories
+ Editar la imatge de la portada
+ "Ordenar ascendent "
+ Ordenar descendent
+ Descarregat
+ Pròxim sense llegir
+ Començar
+ Parar
+ Pausa
+ Borrar
+ Tancar
+ Capítol anterior
+ Següent capítol
+ Reintentar
+ Eliminar
+ Continuar
+ Moure
+ Obrir en el navegador
+ Afegir a la pantalla d\'inici
+ Canviar el mode de visualització
+ Mostrar
+ Quadrícula
+ Llista
+ Icona de descàrregues
+ Establir filtre
+ Cancel·lar
+ Ordenar
+ Instal·lar
+ Compartir
+ Guardar
+ Reiniciar
+ Desfer
+ Exportar
+ Obrir registre
+ Crear
+ Restablir
+ Obrir
+ Iniciar sessió
+ Eliminant…
+ Carregant…
+ Aplicació no disponible
+ Actualitzacions
+ General
+ Lector
+ Descàrregues
+ Origens
+ Seguiment
+ Avançat
+ Sobre el
+ Mangas per fila
+ Vertical
+ Horitzontal
+ Per defecte
+ Freqüència d\'actualització de la biblioteca
+ Manual
+ Cada hora
+ Cada 2 hores
+ Cada 3 hores
+ Cada 6 hores
+ Cada 12 hores
+ Diàriament
+ Cada 2 dies
+ Cada setmana
+ Mensual
+ Categories per incloure en l\'actualització global
+ Tot
+ Restriccions d\'actualització de la biblioteca
+ Actualitzar només quan es compleixen les condicions
+ Wi-Fi
+ Carregant
+ Només actualitzar el manga en curs
+ Sincronitzar capítols després de llegir-los
+ Tema de l\'aplicació
+ Tema principal
+ Tema fosc
+ Tema AMOLED
+ Blau fosc
+ Pantalla d\'inici
+ Idioma
+ Per defecte del sistema
+ Categoria per defecte
+ Preguntar sempre
+ Tot
+ Detalls
+ Actualitzar
+ Instal·lar
+ Pendent
+ S\'està baixant
+ S\'està instal·lant
+ Instal·lat
+ Confiança
+ No és de confiança
+ Desinstal·lar
+ Preferències
+ Disponible
+ Extensió no confiable
+ Aquesta extensió s\'ha firmat amb un certificat que no és de confiança i no s\'ha activat.
+\n
+\nUna extensió maliciosa podria llegir qualsevol credencial guardada en Tachiyomi o executar codi maliciós.
+\n
+\nSi acceptes aquest certificat estàs acceptant els riscos que comporta.
+ Versió: %1$s
+ Idioma: %1$s
+ No hi han preferències per editar en aquesta extensió
+ Pantalla completa
+ Bloquejar l\'orientació
+ Transicions de pàgina
+ Mostra el número de pàgina
+ Retallar les vores
+ Fer servir lluminositat personalitzada
+ Fer servir filtre de color personalitzat
+ Romandre amb la pantalla encesa
+ Navegació
+ Tecles de volum
+ Invertir tecles de volum
+ Pitjar pantalla
+ Color de fons
+ Blanc
+ Negre
+ Lector per defecte
+ Per defecte
+ D\'esquerra a dreta
+ De dreta a esquerra
+ Vertical
+ Webtoons
+ Paginat
+ Descodificador d\'imatge
+ Tipus d\'escalat
+ Ajust de pantalla
+ Estirat
+ Ajust d\'amplada
+ Ajust d\'altura
+ Mida Original
+ Ajust intel·ligent
+ Posició d\'inici del zoom
+ Automàtic
+ Esquerra
+ Dreta
+ Centre
+ Sense animació
+ Normal
+ Ràpid
+ Rotació
+ Lliure
+ Bloca
+ Forçar vertical
+ Forçar apaïsat
+ R
+ G
+ B
+ A
+ Directori de baixades
+ Baixades només amb WIFI
+ Eliminar quan es marqui com llegit
+ Eliminar després de llegir
+
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
new file mode 100644
index 000000000..70545bc02
--- /dev/null
+++ b/app/src/main/res/values-cs/strings.xml
@@ -0,0 +1,457 @@
+
+Kategorie
+ Manga
+ Kapitoly
+ Historie
+
+ Nastavení
+ Moje knihovna
+ Nedávno čteno
+ Poslední aktualizace
+ Kategorie
+ Vybráno: %1$d
+ Záloha
+ Zdroj migrace
+ Rozšíření
+ Informace o rozšíření
+
+
+ Nastavení
+ Filtr
+ Staženo
+ Nepřečteno
+ Přečteno
+ Odstranit filtr
+ Abecedně
+ Naposledy čteno
+ Poslední aktualizace
+ Vyhledávání
+ Globální vyhledávání
+ Vybrat vše
+ Označit jako přečtené
+ Označit jako nepřečtené
+ Označit předchozí jako přečtené
+ Stáhnout
+ Odstranit
+ Upravit
+ Přidat
+ Přidat kategorii
+ Upravit kategorie
+ Staženo
+ Zavřít
+ Předchozí kapitola
+ Následující kapitola
+ Odstranit
+ Otevřít v prohlížeči
+ Přidat na plochu
+ Mřížka
+ Seznam
+ Zrušit
+ Instalovat
+ Sdílet
+ Uložit
+ Zpět
+ Exportovat
+ Vytvořit
+ Obnovit
+ Otevřít
+ Přihlásit se
+
+ Načítání…
+
+ Aplikace není k dispozici
+ Stahování
+ Každou hodinu
+ Každé 2 hodiny
+ Každé 3 hodiny
+ Každých 6 hodiny
+ Každých 12 hodiny
+ Denně
+ Každé 2 dny
+ Týdně
+ Měsíčně
+ Aktualizovat pouze vycházející mangy
+ Synchronizovat kapitoly po přečtení
+ Téma aplikace
+ Hlavní téma
+ Tmavé téma
+ AMOLED téma
+ Tmavě modrá
+ Úvodní obrazovka
+ Jazyk
+ Výchozí
+ Vše
+ Detaily
+ Aktualizovat
+ Instalovat
+ Čeká se
+ Stahování
+ Instalování
+ Nainstalováno
+ Důvěryhodné
+ Nedůvěryhodné
+ Odinstalovat
+ Předvolby
+ Dostupné
+ Nedůvěryhodné rozšíření
+ Toto rozšíření bylo podepsané nedůvěryhodným certifikátem a nebylo aktivované.
+\n
+\nŠkodlivé rozšíření může přečíst přihlašovací údaje uložené v Tachiyomi nebo spustit libovolný kód.
+\n
+\nDůvěřováním tomuto certifikátu přijímáte tato rizika.
+ Verze: %1$s
+ Jazyk: %1$s
+ Neexistují žádné předvolby k úpravám pro toto rozšíření
+
+ Celá obrazovka
+ Přechody stránek
+ Zobrazit číslo stránky
+ Tlačítka hlasitosti
+ Prohodit tlačítka hlasitosti
+ Barva pozadí
+ Bílá
+ Černá
+ Zleva doprava
+ Zprava doleva
+ Otáčení
+ Volné
+ Zamknuté
+ Na výšku
+ Na šířku
+ R
+ G
+ B
+ A
+
+
+ Složka pro stahování
+ Stahovat pouze na Wi-Fi
+ Odstranit po označení jako přečtené
+ Odstranit po čtení
+ Vlastní adresář
+ Stahovat nové kapitoly
+ Uživatelské jméno
+ Heslo
+ Zobrazit heslo
+ Přihlásit se
+ Přihlášení úspěšné
+ Chyba při přihlašování
+ Neznámá chyba
+
+ Opravdu chcete odstranit vybrané mangy\?
+ Žádné další výsledky
+ Manga byla přidána do vaší knihovny
+ Globální vyhledávání…
+ Žádné výsledky!
+ Manga byla odstraněna z databáze!
+
+ Info
+ Popis
+ Vycházející
+ Odstranit z knihovny
+ Název
+ Přidáno do knihovny
+ Odstraněno z knihovny
+ Autor
+ Kapitoly
+ Poslední kapitola
+ Zdroj
+ Žánry
+ Zástupce byl přidán na plochu.
+ Nepodařilo se vytvořit zástupce!
+ Smazat stažené kapitoly\?
+ Zdroj není nainstalována: %1$s
+
+ Kapitoly
+ Bez názvu
+ Kapitola %1$s
+ Stažené
+ Stahování
+ Stahování (%1$d/%2$d)
+ Chyba
+ Pozastaveno
+ Podle zdroje
+ Stáhnout
+ Stáhnout další kapitolu
+ Stáhnout dalších 5 kapitol
+ Stáhnout dalších 10 kapitol
+ Stáhnout vlastní
+ Stáhnout vše
+ Stáhnout nepřečtené
+ Opravdu chcete smazat vybrané kapitoly\?
+
+ Čtení
+ Kompletní
+ Plánováno číst
+ Znovu čtení
+ Skóre
+ Název
+ Typ
+ Autor
+ Restartovat všechny kapitoly mangy
+
+ Přidat mangu do knihovny\?
+
+ Obrázek uložen
+ Ukládání obrázku
+ Možnosti
+
+ Vlastní filtr
+ Stránka zkopírována do %1$s
+ Stahování…
+ Staženo %1$d%%
+ Stránka: %1$d
+ Kapitola %1$s
+ Další kapitola nenalezena
+ Předchozí kapitola nenalezena
+ Obrázek nemohl být dekódován
+ Dokončeno:
+ Aktuální:
+ Následující:
+ Předchozí:
+ Žádná další kapitola
+ Žádná předchozí kapitola
+ Načítání stránek…
+ Vybrat
+ Migrovat
+ Kopírovat
+ Migrovaní…
+
+ Došlo k chybě při stahování kapitol. Zkuste to znovu v sekci stahování
+
+ Postup aktualizace: %1$d/%2$d
+ Nalezeny nové kapitoly
+ Synchronizace zrušena
+ Bez připojení k AC zdroji
+ Synchronizace zrušena
+ Připojení není k dispozici
+
+ Vybrat záložní soubor
+ Vybrat ikonu zástupce
+
+ Dostupná nová aktualizace!
+ Stáhnout
+ Ignorovat
+ Žádné nové aktualizace
+ Hledání aktualizací
+
+ Stáhnout aktualizaci
+ Stahování dokončeno
+ Chyba při stahování
+ Dostupná aktualizace
+
+ Žádné nedávné kapitoly
+ Žádné nedávno čtené mangy
+ Chyba
+ Došlo k neznámé chybě při stahování kapitoly
+ V adresáři chybí stránka
+ Stránka nebyla načtena
+ WiFi připojení není dostupné
+ Síťové připojení není dostupné
+ Stahování pozastaveno
+
+ Knihovna
+ Stahovací fronta
+ Název
+ Sledování
+ Katalogy
+ Aktualizace knihovny
+ Založeno
+ Celkem kapitol
+ Založit
+ Odstranit záložku
+ Aktualizovat
+ Aktualizovat knihovnu
+ Přejmenovat kategorii
+ Přesunout do kategorií
+ Start
+ Stop
+ Pauza
+ Vymazat
+ Opakovat
+ Pokračovat
+ Změnit režim zobrazení
+ Zobrazení
+ Odznáček stažení
+ Nastavit filtr
+ Seřadit
+ Resetovat
+ Otevřít log
+ Mazání…
+ Aktualizace
+ Obecné
+ Čtečka
+ Zdroje
+ Sledování
+ Pokročilé
+ O aplikaci
+ Počet mangy na řádek v knihovně
+ Na výšku
+ Na šířku
+ Výchozí
+ Frekvence aktualizace knihovny
+ Ručně
+ Vše
+ Wi-Fi
+ Nabíjení
+ Výchozí kategorie
+ Vždy se zeptat
+ Zámek orientace
+ Rychlost animace při dvojkliku
+ Oříznout okraje
+ Použít vlastní jas
+ Použít vlastní barevný filtr
+ Klepnutím
+ Výchozí prohlížeč
+ Výchozí
+ Vertikální
+ Webtoon
+ Typ úpravy velikosti
+ Přizpůsobit obrazovce
+ Natáhnout
+ Přizpůsobit šířku
+ Přizpůsobit šířku
+ Původní velikost
+ Chytré přizpůsobení
+ Výchozí poloha zvětšení
+ Automaticky
+ Vlevo
+ Vpravo
+ Na střed
+ Bez animace
+ Normální
+ Rychle
+ Služby
+ Záloha
+ Vytvořit zálohu
+ Obnovit zálohu
+ Adresář zálohy
+ Služba
+ Frekvence zálohy
+ Obnovování zálohy
+\n%1$s přidáno do knihovny
+ Zdroj nebyl nalezen
+ Obnovování zálohy
+\n%1$s zdrojů nenalezeno
+ Záloha vytvořena
+ Obnova kompletní
+ Nelze otevřít log
+ Co chcete zálohovat\?
+ Obnovování zálohy
+ Vytváření zálohy
+ Vymazat mezipaměť kapitol
+ Využito: %1$s
+ Mezipaměť smazána. %1$d souborů bylo odstraněno
+ Během vymazávání mezipaměti došlo k chybě
+ Vymazat cookies
+ Cookies vymazány
+ Vyčistit databázi
+ Smaže mangy a kapitoly, které nejsou ve vaší knihovně
+ Jste si jistý\? Přečtené kapitoly a postup v mangách mimo knihovnu budou ztraceny
+ Položky byly smazány
+ Obnovit metadata knihovny
+ Obnovit metadata sledování
+ Aktualizuje status, skóre a poslední přečtenou kapitolu ze sledovacích služeb
+ Verze
+ Čas sestavení
+ Zkontrolovat aktualizace
+ Odesílat hlášení o pádu
+ Pomáhá opravit chyby. Nebudou odeslány žádné citlivé údaje
+ Aktualizace kategorie
+ Lokální
+ Odstranit také stažené kapitoly
+ Filtry vyhledávání
+ Tento zdroj vyžaduje přihlášení
+ Vybrat zdroj
+ Povolte prosím aspoň jeden platný zdroj
+ Lokální manga
+ Ostatní
+ Nejnovější
+ Procházet
+ Neznámý
+ Licencovaný
+ Kreslíř
+ Aktualizováno
+ Stav
+ Kruhová ikona
+ Zaoblená ikona
+ Čtvercová ikona
+ Hvězdicová ikona
+ Název zástupce
+ Tvar ikony
+ %1$s zkopírováno do schránky
+ Ve frontě
+ Chyba při načítání kapitol
+ Režim seřazení
+ Podle čísla kapitoly
+ Stažení vlastního počtu
+ počet
+ Sledování
+ Upuštěno
+ Pozastaveno
+ Stav
+ Stav
+ Započato
+ Kategorie smazány
+ Nastavit jako obal
+ Obal aktualizován
+ Upravit obrázek obalu
+ Seřadit vzestupně
+ Seřadit sestupně
+ Další nepřečtené
+ Přesunout
+ Kategorie zahrnuté do globální aktualizace
+ Omezení aktualizace knihovny
+ Aktualizovat pouze pokud jsou splněny podmínky
+ Udržovat obrazovku zapnutou
+ Navigování
+ Stránkovač
+ Dekodér obrazu
+ Zakázáno
+ Poslední přečtená kapitola
+ Předposlední kapitola
+ Třetí až poslední kapitola
+ Čtvrtá až poslední kapitola
+ Pátá až poslední kapitola
+ Kategorie zahrnuté do stahování
+ Lze použít k obnovení aktuální knihovny
+ Obnovit knihovnu ze záložního souboru
+ Maximální automatické zálohování
+ Obnovení trvalo %1$s.
+\nNalezené chyby: %2$s.
+ Obnovení používá zdroje pro načítání dat, mohou být účtovány poplatky od operátora (v případě použití datového připojení).
+\nTaké se před obnovením ujistěte, že jste správně přihlášeni do zdrojů, které to vyžadují.
+ Soubor uložen do %1$s
+ Volby dialogu se vynulují
+ Aktualizuje obaly, žánry, popisy a informace o stavu mangy
+ Automaticky kontrolovat aktualizace aplikace
+ Přihlášení pro %1$s
+ Název nebo autor…
+ Výchozí nemůže být vybrána s ostatními kategoriemi
+ Zobrazit název
+ Zobrazit číslo kapitoly
+ Url mangy není nastaveno, klikněte prosím na název a vyberte mangu znovu
+ Kategorie s tímto jménem již existuje!
+ Toto odstraní datum přečtení této kapitoly. Jste si jistý\?
+ Aktualizovat poslední přečtenou kapitolu v povolených službách na %1$d\?
+ Chcete tento obrázek nastavit jako obal\?
+ Prohlížeč této série
+ Chyba při načítání stránek: %1$s
+ %1$s - K.%2$s
+ Klepnutím vyberte zdroj, ze kterého chcete migrovat
+ Vyberte data, která chcete zahrnout
+ Pro %1$d titulů
+ Chyba při aktualizaci obalu
+ Přidejte si prosím mangu do knihovny před tím, než toto uděláte
+ Vybrat obrázek obalu
+ Stahování začalo
+ Probíhá stahování
+ Obrázek pozadí manga
+ Obal mangy
+ Žádné stahování
+ Vaše knihovna je prázdná, můžete si sem přidat něco z Katalogů.
+ Nemáte žádné kategorie. Klikněte na tlačítko plus a vytvořte nějakou pro organizaci vaší knihovny.
+ Stahovač
+ Běžný
+ Stahovač
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 7878122ae..09b203536 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -46,7 +46,6 @@
Vorschaubild bearbeiten
Aufsteigend sortieren
Absteigend Sortieren
- Ungelesen
Heruntergeladen
Nächstes Ungelesenes
Start
@@ -83,7 +82,7 @@
Wird geladen…
App nicht verfügbar
- Updates
+ Aktualisierungen
Allgemein
Lese-Einstellungen
@@ -116,7 +115,6 @@
Am Laden
Nur fortlaufende Manga aktualisieren
Kapitel nach dem Lesen synchronisieren
- Vor Update bestätigen
App Design
Hauptdesign
Dunkles Design
@@ -173,7 +171,6 @@
Downloadordner
- Simultane Downloads
Nur über WLAN herunterladen
Gelesene Kapitel löschen
Gelesene Kapitel löschen ab dem
@@ -268,7 +265,6 @@
Fortlaufend
Unbekannt
Lizenziert
- Zur Bibliothek hinzufügen
Aus Bibliothek entfernen
Autor
Künstler
@@ -341,8 +337,7 @@
Kapitel %1$s
Nächstes Kapitel nicht gefunden
Vorheriges Kapitel nicht gefunden
- Bild konnte nicht geladen werden.
-\nVersuche entweder den Bilddecoder zu ändern oder probiere eine der Optionen weiter unten aus
+ Das Bild konnte nicht dekodiert werden
Setze das letzte gelesen Kapitel auf %1$d?
Wollen Sie dieses Bild als Vorschaubild setzen?
Leser dieser Serie
@@ -461,4 +456,25 @@
Benutzerdefinierte Anzahl herunterladen
Wähle die Quelle von welcher Migriert werden soll
Wähle zu beinhaltende Daten
+ Dunkelblau
+ Seiten
+ Quelle nicht installiert: %1$s
+
+ Benutzerdefinition herunterladen
+ Erneutes Lesen
+ Stand
+ Gestartet
+ Typ
+ Autor
+ Manga url ist nicht gesetzt. Bitte auf den Titel drücken und den Manga erneut wählen
+
+ Beendet:
+ Aktuell:
+ Nächste:
+ Vorherige:
+ Es gibt kein nächstes Kapitel
+ Es gibt kein vorheriges Kapitel
+ Lade Seiten…
+ Seiten konnten nicht geladen werden: %1$s
+
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
new file mode 100644
index 000000000..00d75d39d
--- /dev/null
+++ b/app/src/main/res/values-el/strings.xml
@@ -0,0 +1,476 @@
+
+Όνομα
+ Κατηγορίες
+ Manga
+ Κεφάλαια
+ Παρακολούθηση
+ Ιστορία
+
+ Ρυθμίσεις
+ Ουρά λήψεων
+ Η βιβλιοθήκη μου
+ Πρόσφατα αναγνωρισμένο
+ Κατάλογοι
+ Ενημερώσεις βιβλιοθήκης
+ Τελευταίες ενημερώσεις
+ Κατηγορίες
+ Επιλεγμένο: %1$d
+ Αντίγραφο ασφάλειας
+ Μεταφορά πηγής
+ Επεκτάσεις
+ Πληροφορίες επέκτασης
+
+
+ Ρυθμίσεις
+ Φίλτρο
+ Λήψεις
+ Σελιδοδείκτες
+ Αδιάβαστα
+ Διαβασμένα
+ Αφαίρεση φίλτρου
+ Αλφαβητικά
+ Σύνολο κεφαλαίων
+ Τελευταίο αναγνωρισμένο
+ Τελευταίο ενημερωμένο
+ Αναζήτηση
+ Καθολική αναζήτηση
+ Επιλογή όλων
+ Σήμανση ως αναγνωρισμένο
+ Σήμανση ως μη αναγνωρισμένο
+ Σήμανση προηγούμενου ως αναγνωρισμένο
+ Λήψη
+ Σελιδοδείκτης
+ Διαγραφή σελιδοδείκτη
+ Διαγραφή
+ Ενημέρωση
+ Ενημέρωση βιβλιοθήκης
+ Επεξεργασία
+ Προσθήκη
+ Προσθήκη κατηγορίας
+ Επεξεργασία βιβλιοθήκης
+ Μετονομασία βιβλιοθήκης
+ Μετακίνηση σε κατηγορίες
+ Επεξεργασία εξώφυλλου
+ Ταξινόμηση κατά αύξουσα σειρά
+ Ταξινόμηση κατά φθίνουσα σειρά
+ Λήψεις
+ Επόμενο μη αναγνωρισμένο
+ Εκκίνηση
+ Σταμάτημα
+ Παύση
+ Απαλοιφή
+ Κλείσιμο
+ Προηγούμενο κεφάλαιο
+ Επόμενο κεφάλαιο
+ Δοκίμασε ξανά
+ Διαγραφή
+ Επανεκκίνηση
+ Μεταφορά
+ Άνοιγμα σε πρόγραμμα περιήγησης
+ Προσθήκη στην αρχική οθόνη
+ Αλλαγή τρόπου προβολής
+ Οθόνη
+ Πλέγμα
+ Λίστα
+ "Λήψη κονκάρδων "
+ Όρισμα φίλτρου
+ Ακύρωση
+ Ταξινόμηση
+ Εγκατάσταση
+ Κοινή χρήση
+ Αποθήκευση
+ Επαναφορά
+ Αναίρεση
+ Εξαγωγή
+ Άνοιγμα αρχείου καταγραφής
+ Δημιουργία
+ Επαναφορά
+ Άνοιγμα
+ Εισαγωγή
+
+ Διαγραφή…
+ Φόρτωση…
+
+ Μη διαθέσιμη εφαρμογή
+ Ενημερώσεις
+
+ Γενικά
+ Αναγνώστης
+ Λήψεις
+ Πηγές
+ Παρακολούθηση
+ Προχωρημένο
+ Περί
+
+ Βιβλιοθήκη manga ανά σειρά
+ Κατακόρυφος
+ Οριζόντιος
+ Προεπιλογή
+ Συχνότητα ενημέρωσης βιβλιοθήκης
+ Εγχειρίδιο
+ Ωριαία
+ Κάθε 2 ώρες
+ Κάθε 3 ώρες
+ Κάθε 6 ώρες
+ Κάθε 12 ώρες
+ Καθημερινά
+ Κάθε 2 ημέρες
+ Εβδομαδιαία
+ Μηνιαία
+ Κατηγορίες που περιλαμβάνονται στην ολική ενημέρωση
+ Όλα
+ Περιορισμοί ενημέρωσης βιβλιοθήκης
+ Ενημέρωση μόνο όταν πληρούνται οι προϋποθέσεις
+ Wi-Fi
+ Φόρτωση
+ Ενημέρωση μόνο manga σε εξέλιξη
+ Συγχρονισμός κεφάλαιων μετά τη ανάγνωση
+ Θέμα εφαρμογής
+ Κύριο θέμα
+ Σκοτεινό θέμα
+ Θέμα AMOLED
+ Σκούρο μπλε
+ Οθόνη εκκίνησης
+ Γλώσσα
+ Προεπιλογή συστήματος
+ Προεπιλεγμένη κατηγορία
+ Ερώτηση πάντα
+
+ Όλα
+ Λεπτομέρειες
+ Ενημέρωση
+ Εγκατάσταση
+ Αναμονή
+ Μεταφορά
+ Εγκατάσταση
+ Εγκαταστημένο
+ Εμπιστοσύνη
+ Μη αξιόπιστο
+ Απεγκατάσταση
+ Προτιμήσεις
+ Διαθέσιμο
+ Μη αξιόπιστη επέκταση
+ Αυτή η επέκταση υπογράφηκε με μη αξιόπιστο πιστοποιητικό και δεν ενεργοποιήθηκε.
+\n
+\nΜια κακόβουλη επέκταση θα μπορούσε να διαβάσει τα διαπιστευτήρια σύνδεσης που είναι αποθηκευμένα στο Tachiyomi ή να εκτελέσει αυθαίρετο κώδικα.
+\n
+\nΕμπιστεύοντας αυτό το πιστοποιητικό αποδέχεστε αυτούς τους κινδύνους.
+ Έκδοση: %1$s
+ Γλώσσα: %1$s
+ Δεν υπάρχουν προτιμήσεις επεξεργασίας για αυτή την επέκταση
+
+ Πλήρης οθόνη
+ Κλείδωμα προσανατολισμού
+ Αλλαγές σελίδας
+ Διπλή βρύση κινούμενα σχέδια ταχύτητα
+ Εμφάνιση αριθμού σελίδας
+ Περικοπή άκρων
+ Χρήση εξατομικευμένης φωτεινότητας
+ Χρήση εξατομικευμένου φίλτρου χρωμάτων
+ Να κρατηθεί η οθόνη ανοιχτή
+ Πλοήγηση
+ Πλήκτρα έντασης ήχου
+ Αναστροφή πλήκτρων έντασης
+ Άγγιγμα
+ Χρώμα παρασκηνίου
+ Άσπρο
+ Μαύρο
+ Προεπιλεγμένος αναγνώστης
+ Προεπιλογή
+ Αριστερά προς τα δεξιά
+ Δεξιά προς τα αριστερά
+ Κάθετα
+ Webtoon
+ Σελιδοποίηση
+ Αποκωδικοποιητής εικόνας
+ Τύπος κλίμακας
+ Προσαρμογή στην οθόνη
+ Τέντωμα
+ Προσαρμογή στο πλάτος
+ Προσαρμογή στο ύψος
+ Αρχικό μέγεθος
+ Έξυπνη προσαρμογή
+ Μεγέθυνση αρχικής θέσης
+ Αυτόματα
+ Αριστερά
+ Δεξιά
+ Κέντρο
+ Χωρίς κίνηση
+ Κανονικό
+ Γρήγορο
+ Περιστροφή
+ Ελεύθερο
+ Κλέιδωμα
+ Υποχρεωτικά κατακόρυφα
+ Υποχρεωτικά οριζόντια
+ R
+ G
+ B
+ A
+
+
+ Φάκελος λήψεων
+ Λήψη μόνε με Wi-Fi
+ Διαγραφή αν σημειώνεται ως αναγνωσμένο
+ Διαγραφή μετά την ανάγνωση
+ Εξατομικευμένος φάκελος
+ Απενεργοποιημένο
+ Τελευταίο αναγνωσμένο κεφάλαιο
+ Προτελευταίο αναγνωσμένο κεφάλαιο
+ Προ-προτελευταίο αναγνωσμένο κεφάλαιο
+ Προ-προ-προτελευταίο αναγνωσμένο κεφάλαιο
+ Προ-προ-προ-προτελευταίο αναγνωσμένο κεφάλαιο
+ Λήψη νέων κεφάλαιων
+ Κατηγορίες που να συμπεριλαμβάνονται στην λήψη
+
+ Υπηρεσίες
+
+ Αντίγραφο ασφαλείας
+ Δημιουργία αντίγραφου ασφαλείας
+ Μπορεί να χρησιμοποιηθεί για επαναφορά τρέχουσας βιβλιοθήκης
+ Επαναφορά αντίγραφου ασφάλειας
+ Επαναφορά βιβλιοθήκης από αρχείο αντίγραφου ασφάλειας
+ Φάκελος αντίγραφου ασφάλειας
+ Υπηρεσία
+ Συχνότητα αντίγραφου ασφάλειας
+ Μέγιστος αριθμός αυτόματων αντιγραφών ασφάλειας
+ Επαναφορά αντίγραφου ασφάλειας
+\n%1$s προστέθηκε στη βιβλιοθήκη
+ "Δεν βρέθηκε πηγή "
+ Επαναφορά αντίγραφου ασφάλειας %1$s
+\nΔεν βρέθηκε πηγή
+ Δημιουργήθηκε αντίγραφο ασφαλείας
+ Η επαναφορά ολοκληρώθηκε
+ Δεν ήταν δυνατό το άνοιγμα αρχείου καταγραφής
+ Η επαναφορά διήρκησε %1$s.
+\nΒρέθηκαν %2$s σφάλματα.
+ Η επαναφορά χρησιμοποιεί την πηγή για την μεταφορά δεδομένων, ενδέχεται να ισχύουν έξοδα φορέα.
+\nΕπίσης, βεβαιωθείτε ότι είστε σωστά συνδεδεμένοι σε πηγές που χρειάζονται σύνδεση, πριν από την αποκατάσταση.
+ Το αρχείο αποθηκεύτηκε στο %1$s
+ Τι θέλετε να κάνετε backup;
+ Επαναφορά αντιγράφων ασφαλείας
+ Δημιουργία αντιγράφων ασφαλείας
+
+ Καθάρισμα προσωρινής μνήμης κεφαλαίου
+ Χρησιμοποιήθηκε: %1$s
+ Η κρυφή μνήμη διαγράφηκε. %1$d αρχεία έχουν διαγραφεί
+ Παρουσιάστηκε σφάλμα κατά την εκκαθάριση της προσωρινής μνήμης
+ Διαγραφή cookies
+ Τα cookies διαγράφηκαν
+ Οι επιλογές διαλόγου επαναφέρθηκαν
+ Καθαρισμός βάσης δεδομένων
+ Διαγραφή manga και κεφάλαιων που δεν περιλαμβάνονται στη βιβλιοθήκη σας
+ Είσαι σίγουρος\? Τα διαβασμένα κεφάλαια και η πρόοδος των manga εκτός βιβλιοθήκης θα χαθεί
+ Οι καταχωρίσεις διαγράφηκαν
+ Ανανέωση μεταδεδομένων βιβλιοθήκης
+ Ενημερώνει εξώφυλλα, είδη, περιγραφή και πληροφορίες κατάστασης του manga
+ Ανανέωση μεταδεδομένων παρακολούθησης
+ Ενημερώνει κατάσταση, βαθμολογία και τελευταίο αναγνωσμένο κεφάλαιο από τις υπηρεσίες παρακολούθησης
+
+ Έκδοση
+ Χρόνος κατασκευής
+ Έλεγχος για ενημερώσεις
+ Αυτόματος έλεγχος για ενημερώσεις εφαρμογής
+ Αποστολή αναφορών σφαλμάτων
+ Βοηθά στην επιδιόρθωση τυχόν σφαλμάτων. Δεν θα αποστέλλονται ευαίσθητα δεδομένα
+
+
+ Σύνδεση για %1$s
+ Όνομα χρήστη
+ Κωδικός πρόσβασης
+ Δείξε τον κωδικό
+ Σύνδεση
+ Επιτυχής σύνδεση
+ Σφάλμα σύνδεσης
+ Άγνωστο σφάλμα
+
+ Τίτλος ή συγγραφέας…
+ Ενημέρωση κατηγορίας
+ Τοπικό
+ Είστε βέβαιοι ότι θέλετε να καταργήσετε τα επιλεγμένα manga;
+ "Επίσης, διαγραφή κεφάλαιων που έχουν ληφθεί "
+
+ Φίλτρα αναζήτησης
+ Αυτή η πηγή απαιτεί να συνδεθείτε
+ Επιλέξτε πηγή
+ Ενεργοποιήστε τουλάχιστον μία έγκυρη πηγή
+ Δεν υπάρχουν άλλα αποτελέσματα
+ Τοπικά manga
+ Άλλα
+ Δεν είναι δυνατή η επιλογή προεπιλογής με άλλες κατηγορίες
+ Το manga έχει προστεθεί στη βιβλιοθήκη σας
+\n
+ Καθολική αναζήτηση…
+ Δεν βρέθηκαν αποτελέσματα!
+ Τελευταίο
+ Ξεφύλλισμα
+
+ Αυτό το manga αφαιρέθηκε από τη βάση δεδομένων!
+
+ Πληροφορίες
+ Περιγραφή
+ Σε εξέλιξη
+ Άγνωστο
+ Αδειούχος
+ Κατάργηση από τη βιβλιοθήκη
+ Τίτλος
+ Προστέθηκε στη βιβλιοθήκη
+ Αφαιρέθηκε από τη βιβλιοθήκη
+ Συγγραφέας
+ Καλλιτέχνης
+ Κεφάλαια
+ Τελευταίο κεφάλαιο
+ Ενημερωμένο
+ Κατάσταση
+ Πηγή
+ Είδη
+ Κυκλικό εικονίδιο
+ "Στρογγυλεμένο εικονίδιο "
+ Τετράγωνο εικονίδιο
+ Εικονίδιο αστέρι
+ Συντόμευση τίτλου
+ Η συντόμευση προστέθηκε στην αρχική οθόνη.
+ Σχήμα εικονιδίου
+ Αποτυχία δημιουργίας συντόμευσης!
+ Να διαγραφούν τα κεφάλαια που έχουν ληφθεί;
+ %1$s αντιγράφτηκε στο πρόχειρο
+ Η πηγή δεν είναι εγκατεστημένη: %1$s
+
+ Κεφάλαια
+ Χωρίς τίτλο
+ Κεφάλαιο %1$s
+ Λήψη
+ Στην ουρά
+ Λήψη
+ Λήψη (%1$d/%2$d)
+ Σφάλμα
+ Παύση
+ Σφάλμα κατά την ανάκτηση κεφαλαίων
+ Εμφάνιση τίτλου
+ Εμφάνιση αριθμού κεφαλαίου
+ Λειτουργία ταξινόμησης
+ Με βάση την πηγή
+ Ανά αριθμό κεφαλαίου
+ Λήψη
+ Λήψη προσαρμοσμένου ποσού
+ ποσό
+ Λήψη επόμενου κεφαλαίου
+ Λήψη επόμενων 5 κεφαλαίων
+ Λήψη επόμενων 10 κεφαλαίων
+ "Προσαρμοσμένη λήψη "
+ Λήψη όλων
+ Λήψη μη αναγνωσμένων
+ Είστε βέβαιοι ότι θέλετε να διαγράψετε επιλεγμένα κεφάλαια;
+
+ Παρακολούθηση
+ Ανάγνωση
+ Ολοκληρωμένο
+ Παραλείφθηκε
+ Σε αναμονή
+ Προγραμματισμένο για ανάγνωση
+ Δεύτερη ανάγνωση
+ Βαθμολογία
+ Τίτλος
+ Κατάσταση
+ Κατάσταση
+ Ξεκίνησε
+ Τύπος
+ Συγγραφέας
+ Το Manga url δεν έχει οριστεί, κάντε κλικ στον τίτλο και επιλέξτε ξανά το manga
+
+ Μια κατηγορία με αυτό το όνομα υπάρχει ήδη!
+ Οι κατηγορίες διαγράφηκαν
+
+ Αυτό θα αφαιρέσει την ημερομηνία ανάγνωσης αυτού του κεφαλαίου. Είσαι σίγουρος;
+ Επαναφέρετε όλα τα κεφάλαια για αυτό το μάγκα
+
+ Προσθέστε το manga στη βιβλιοθήκη;
+
+ Η εικόνα αποθηκεύτηκε
+ Αποθήκευση εικόνας
+ Επιλογές
+
+ Προσαρμοσμένο φίλτρο
+ Ορισμός ως εξώφυλλο
+ Εξώφυλλο ενημερώθηκε
+ Η σελίδα αντιγράφτηκε σε %1$s
+ Λήψη…
+ Λήψη %1$d%%
+ Σελίδα: %1$d
+ Κεφάλαιο %1$s
+ Το επόμενο κεφάλαιο δεν βρέθηκε
+ Το προηγούμενο κεφάλαιο δεν βρέθηκε
+ Δεν ήταν δυνατή η αποκωδικοποίηση της εικόνας
+ Ενημέρωση τελευταίου αναγνωσμένο κεφαλαίο στις ενεργοποιημένες υπηρεσίες σε %1$d ;
+ Θέλετε να ορίσετε αυτήν την εικόνα ως εξώφυλλο;
+ Αναγνώστης για αυτήν τη σειρά
+ Διαβασμένο:
+ Τρέχον:
+ Επόμενο:
+ Προηγούμενο:
+ Δεν υπάρχει επόμενο κεφάλαιο
+ Δεν υπάρχει προηγούμενο κεφάλαιο
+ Φόρτωση σελίδων…
+ Η φόρτωση σελίδων απέτυχε: %1$s
+
+ %1$s - Κεφ.%2$s
+
+ Πατήστε για να επιλέξετε την πηγή από την οποία θα μεταφέρετε
+ Επιλέξτε τα δεδομένα που θέλετε να συμπεριλάβετε
+ Επιλογή
+ Μεταφορά
+ Αντιγραφή
+ Μεταφορά…
+
+ Παρουσιάστηκε σφάλμα κατά τη λήψη κεφαλαίων. Μπορείτε να δοκιμάσετε ξανά στην ενότητα λήψεων
+
+ Ενημέρωση προόδου: %1$d/%2$d
+ Βρέθηκαν νέα κεφάλαια
+ Για %1$d τίτλους
+ Δεν ήταν δυνατή η ενημέρωση του εξώφυλλου
+ Παρακαλώ προσθέστε το manga στη βιβλιοθήκη σας πριν κάνετε κάτι τέτοιο
+ Ο συγχρονισμός ακυρώθηκε
+ Δεν είναι συνδεδεμένο με τροφοδοσία
+ Ο συγχρονισμός ακυρώθηκε
+ Η σύνδεση δεν είναι διαθέσιμη
+
+ Επιλέξτε εικόνα εξωφύλλου
+ Επιλέξτε αρχείο αντιγράφου ασφαλείας
+ Επιλέξτε εικονίδιο συντόμευσης
+
+ Νέα διαθέσιμη ενημέρωση!
+ Λήψη
+ Παράλειψη
+ Δεν υπάρχουν διαθέσιμες νέες ενημερώσεις
+ Ξεκίνησε η λήψη
+ Αναζήτηση για ενημερώσεις
+
+ Λήψη ενημέρωσης
+ Λήψη σε εξέλιξη
+ Η λήψη ολοκληρώθηκε
+ Σφάλμα λήψης
+ Υπάρχει διαθέσιμη ενημέρωση
+
+ Εικόνα παρασκηνίου του manga
+ Εξώφυλλο manga
+
+ Δεν υπάρχουν λήψεις
+ Δεν υπάρχουν πρόσφατα κεφάλαια
+ Δεν υπάρχουν πρόσφατα αναγνωσμένα manga
+ Η βιβλιοθήκη σας είναι κενή, μπορείτε να προσθέσετε σειρές στη βιβλιοθήκη σας από τους καταλόγους.
+ Δεν έχετε κατηγορίες. Χτυπήστε το πλήκτρο συν για να δημιουργήσετε ένα για την οργάνωση της βιβλιοθήκης σας.
+
+ Downloader
+ Σφάλμα
+ Παρουσιάστηκε μη αναμενόμενο σφάλμα κατά τη λήψη του κεφαλαίου
+ Μια σελίδα λείπει από τον κατάλογο
+ Δεν φορτώνεται μια σελίδα
+ Δεν υπάρχει διαθέσιμη σύνδεση wifi
+ Δεν υπάρχει διαθέσιμη σύνδεση δικτύου
+ Λήψη σε παύση
+
+ Κοινό
+ Βιβλιοθήκη
+ Downloader
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 0b51a2828..bd4d71247 100755
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -1,9 +1,8 @@
-
+
Nombre
-
- Opciones
+ Configuraciones
Cola de descargas
Mi librería
Leído Recientemente
@@ -12,10 +11,9 @@
Categorías
Seleccionados: %1$d
Copia de seguridad
-
- Opciones
- Filtro
+ Configuraciones
+ Filtrar
Descargado
No leído
Eliminar filtros
@@ -36,7 +34,6 @@
Editar la imagen de la portada
Ordenar ascendentemente
Ordenar descendentemente
- No leído
Descargado
Próximo sin leer
Comenzar
@@ -53,13 +50,11 @@
Cancelar
Ordenar
Instalar
-
Eliminando…
Cargando…
-
-
+
General
Lector
Descargas
@@ -67,8 +62,7 @@
Seguimiento
Avanzado
Acerca de
-
-
+
Mangas por fila
Retrato
Apaisado
@@ -88,12 +82,10 @@
Cargando
Solo actualizar el manga en curso
Sincronizar capítulos después de leerlos
- Confirmar antes de actualizar
Tema de la aplicación
Tema principal
Tema oscuro
-
-
+
Pantalla completa
Bloquear orientación
Habilitar transiciones
@@ -111,7 +103,7 @@
Izquierda a derecha
Derecha a izquierda
Vertical
- Webtoon
+ Webtoons
Decodificador de imagen
Tipo de escalado
Ajustar a la pantalla
@@ -130,22 +122,16 @@
Bloqueado
Forzar retrato
Forzar apaisado
-
-
+
Directorio de descargas
- Descargas simultáneas
Descargar solo a través de Wi-Fi
Eliminar al marcar como leído
Eliminar después de leer
Directorio personalizado
-
-
-
-
-
+
+
Servicios
-
-
+
Borrar la caché de los capítulos
Usado: %1$s
Caché borrada. %1$d archivos se han eliminado
@@ -156,8 +142,7 @@
Eliminar solo manga y capítulos que no están en tu librería
¿Estás seguro? Los capítulos leídos y el progreso de manga que no esté en la librería se perderá
Entradas borradas
-
-
+
Versión
Hora de compilación
Buscar actualizaciones
@@ -165,8 +150,6 @@
Enviar informes de fallos
Ayuda a corregir cualquier error. No se enviarán datos sensibles
-
-
Iniciar sesión en %1$s
Nombre de usuario
@@ -176,23 +159,19 @@
Inicio de sesión exitoso
Inicio de sesión inválido
Error desconocido
-
Título o autor…
Actualizando categoría
¿Estás seguro de eliminar los mangas seleccionados?
-
Este sitio requiere que inicies sesión
Selecciona una fuente
-
Info
Descripción
En curso
Desconocido
Licenciado
- Añadir a la librería
Eliminar de la librería
Autor
Artista
@@ -200,7 +179,6 @@
Estado
Fuentes
Géneros
-
Capítulos
Sin título
@@ -214,7 +192,7 @@
Mostrar título
Mostrar número de capítulo
Ordenado de capítulos
- Como en la fuente
+ Por fuente
Por número de capítulo
Descargar
Descargar siguiente capítulo
@@ -223,7 +201,6 @@
Descargar todos
Descargar no leídos
¿Estás seguro de eliminar los capítulos seleccionados?
-
Leyendo
Completo
@@ -234,12 +211,9 @@
Título
Estado
Capítulos
-
Esto eliminará la fecha de lectura de este capítulo. ¿Estás seguro?
Reiniciar todas los capítulos de este manga
-
-
Descargando…
Descargado %1$d%%
@@ -247,19 +221,15 @@
Capítulo %1$s
Siguiente capítulo no encontrado
Capítulo anterior no encontrado
- La imagen no se pudo decodificar. Intente de nuevo cambiándolo o seleccione una de las siguientes opciones
+ "La imagen no se pudo decodificar. "
¿Actualizar el último capítulo leído a %1$d en los servicios activos?
Lectura para esta serie
-
Copia de seguridad
-
%1$s - Ch.%2$s
-
Se ha producido un error al descargar capítulos. Puede intentarlo de nuevo en la sección de descargas
-
Progreso de actualización: %1$d/%2$d
Nuevos capítulos encontrados
@@ -268,11 +238,9 @@
El dispositivo no está cargando
Sincronización cancelada
Sin conexión o restringido a Wi-Fi
-
Seleccione la imagen de portada
Seleccione la copia de seguridad
-
¡Nueva actualización disponible!
Descargar
@@ -280,36 +248,30 @@
No hay nueva actualización
Comenzando descarga
Buscando actualizaciones
-
Descargar actualización
Descarga en progreso
Descarga completa
Error en la descargar
Actualización disponible
-
Imagen de fondo del manga
Imagen de fondo del manga
-
No hay descargas
No hay capítulos recientes
No hay mangas leídos recientes
Tu librería está vacía, puedes agregar series a tu librería desde los Catálogos.
-
Error
Se ha producido un error inesperado al descargar el capítulo
Una página no se encuentra en el directorio
No se ha cargado una página
Conexión Wi-Fi no disponible
-
-Categorías
+ Categorías
Manga
Seguimiento
Historial
-
Últimas Actualizaciones
Marcado
Leído
@@ -322,8 +284,7 @@
Añadir
Conexión a la red no disponible
Descarga detenida
-
-Deseas fijar esta imagen como portada?
+ Deseas fijar esta imagen como portada?
Cerrar
Mover
Añadir a pantalla de inicio
@@ -339,31 +300,26 @@
Abrir registro
Crear
Restablecer
-
Aplicación no disponible
Actualizaciones
-
Semanalmente
Mensualmente
Categorías a incluir en actualización global
- Todo
+ Todos
Tema AMOLED
Pantalla inicial
Idioma
Predefinido del sistema
Categoría predefinida
Siempre preguntar
-
- Recortar bordes
+ Recortar bordes
Utilizar filtro de color personalizado
Invertir teclas de volumen
R
G
B
A
-
-
- Deshabilitado
+ Deshabilitado
Ultimo capitulo leído
Penúltimo capítulo
Antepenúltimo capítulo
@@ -371,8 +327,7 @@
Quinto al último capítulo
Descargar capítulos nuevos
Categorías a incluir en la descarga
-
- Crear copia de seguridad
+ Crear copia de seguridad
Se puede utilizar para restaurar la biblioteca actual
Restaurar copia de seguridad
Restaurar la biblioteca del archivo de copia de seguridad
@@ -396,22 +351,17 @@ También asegúrese de haber iniciado sesión en las fuentes que lo requieren an
Qué deseas respaldar?
Restaurando copia de seguridad
Creando copia de seguridad
-
- Reiniciar opciones de diálogo
+ Reiniciar opciones de diálogo
Actualizar metadatos de la biblioteca
Actualizaciones de portadas, géneros, descripción y estado del manga
Actualizar metadatos de seguimiento
Actualización de estado, puntuación y último capítulo leído en los servicios de seguimiento
-
- También eliminar capítulos descargados
-
+ También eliminar capítulos descargados
Por favor habilita al menos una fuente válida
No hay más resultados
Manga local
El manga ha sido añadido a tu biblioteca
-
Este manga fue removido de la base de datos!
-
Ultimo capítulo
Icono circular
Icono redondeado
@@ -421,18 +371,14 @@ También asegúrese de haber iniciado sesión en las fuentes que lo requieren an
Forma del icono
Error al crear acceso directo!
Eliminar capítulos descargados?
-
Pausado
Seguimiento
Ya existe una categoría con este nombre!
Categorías eliminadas
-
Añadir manga a biblioteca?
-
Imagen guardada
Guardando imagen
Opciones
-
Filtro personalizado
Establecer como cubierta
Cubierta actualizada
@@ -440,25 +386,80 @@ También asegúrese de haber iniciado sesión en las fuentes que lo requieren an
Para %1$d títulos
Error al actualizar la portada
Seleccionar icono de acceso directo
-
Descargador
Predefinido no puede ser seleccionada con otras categorías
Búsqueda global
Icono de descargas
Abrir
Iniciar sesión
-
Local
Otros
Búsqueda global…
Ningún resultado encontrado!
Recientes
Explorar
-
Acceso directo fue agregado a la pantalla de inicio.
Común
Librería
Descargador
-No tienes categorías. Toca el botón más para crear una y organizar tu librería.
-
-
+ No tienes categorías. Toca el botón más para crear una y organizar tu librería.
+ Migración de fuente
+ Extensiones
+ Información de extensión
+ Detalles
+ Actualizar
+ Instalar
+ Pendiente
+ Descargando
+ Instalando
+ Instalado
+ Todo
+ Confiable
+ No confiable
+ Desinstalar
+ Disponible
+ Extensión no confiable
+ Versión: %1$s
+ Idioma: %1$s
+ Normal
+ Rápida
+ cantidad
+ Siguiente:
+ Anterior:
+ No hay capítulo siguiente
+ No hay capítulo anterior
+ Migrar
+ Copiar
+ Migrando…
+ Configuraciones
+ Esta extensión fue firmada por una fuente no certificada y no fue activada.
+\nUna extencion maliciosa puede leer cualquier credencial de inicio guardada en Tachiyomi o ejecutar código arbitrario.
+\nConfiando en este certificado aceptas estos riesgos.
+ No hay preferencias para editar en esta extensión
+ Sin animación
+ Filtros de búsqueda
+ Título
+ Añadido a la biblioteca
+ Quitado de la biblioteca
+ Actualizado
+ %1$s copiado al portapapeles
+ Fuente no instalada: %1$s
+ Descargar cantidad personalizada
+ Descargar personalizado
+ Relectura
+ Estado
+ Empezado
+ Tipo
+ Autor
+ La dirección del manga no está fijada, por favor haga clic en el título y seleccione el manga de nuevo
+ Finalizado:
+ Actual:
+ Cargando páginas…
+ Error al cargar páginas: %1$s
+ Pulse para seleccionar la fuente desde la que migrar
+ Seleccionar datos para incluir
+ Seleccionar
+ Velocidad de animación de doble toque
+ Paginado
+ Azul oscuro
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 4f2e66804..4db5f7f2c 100755
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1,4 +1,4 @@
-
+
Nom
@@ -45,7 +45,6 @@
Changer l\'image de couverture
Trier par ordre ascendant
Trier par ordre descendant
- Non lu
Téléchargé
Prochain non lu
Commencer
@@ -107,7 +106,6 @@
En charge
Ne mettre à jour que les mangas en cours
Synchroniser les chapitres après les avoir lus
- Confirmez avant de mettre à jour
Thème de l\'application
Thème principal
Thème sombre
@@ -159,7 +157,6 @@
Répertoire de téléchargements
- Téléchargements simultanés
Télécharger uniquement via Wi-Fi
Supprimer après avoir lu
Désactivé
@@ -222,7 +219,6 @@
En cours
Inconnu
Licencié
- Ajouter à la bibliothèque
Enlever de la bilbiothèque
Auteur
Artiste
@@ -258,12 +254,12 @@
Suivi
- En lecture
+ En cours
Complété
Abandonné
En pause
- Lecture planifiée
- Ponctuation
+ A lire
+ Note
Titre
Statut
Chapitres
@@ -294,7 +290,7 @@
Chapitre %1$s
Chapitre suivant non trouvé
Chapitre précédent non trouvé
- L\'image n\'a pas pu être chargée.\nEssayez de changer le décodeur d\'image ou une des autres options ci-après
+ L\'image n\'a pas pu être décodée
Mettre à jour le dernier chapitre lu dans les services activés à %1$d ?
Voulez-vous mettre cette image comme couverture ?
Lecteur pour cette série
@@ -406,7 +402,7 @@
Le défaut ne peut pas être selectionné avec des autres catégories
Supprimer les chapitres téléchargés ?
- Pour %1$s chapitres
+ Pour %1$d chapitres
Suivi
Nombre de chapitres
Actualisations
@@ -458,4 +454,72 @@ Assurez-vous que vous êtes connecté à des sources qui le demande avant de com
Téléchargeur
Vous n\'avez aucune catégorie. Appuyez sur le bouton plus pour en créeer un pour organiser votre bibliothèque.
+ Extensions
+ Détails
+ Mettre à jour
+ Installer
+ En attente
+ En cours de téléchargement
+ En cours d\'installation
+ Installée
+ Désinstaller
+ Préférences
+ Disponible
+ Extension non reconnue
+ Cette extension a été signé avec un certificat non reconnu et n\'a pas été activée.
+\n
+\nUne extension malveillante peut lire les certificats de connections sauvegardés sur Tachiyomi ou exécuter du code arbitraire.
+\n
+\nEn faisant confiance à ce certificat vous acceptez ces risques.
+ Version: %1$s
+ Langue: %1$s
+ Aucune préférence n\'est disponible pour cette extension
+
+ Vitesse d\'animation du double-clic
+ Sans animation
+ Normale
+ Rapide
+ Filtre de recherche
+ Titre
+ Ajouté à la bibliothèque
+ Supprimé de la bibliothèque
+ Mis à jour
+ %1$s a été copié dans le presse papier
+ Sources non installées: %1$s
+
+ Télécharger une quantité personnalisée
+ Quantité
+ Téléchargement personalisé
+ Status
+ Démarré
+ Type
+ Auteur
+ L\'url du manga n\'a pas été entrée, veuillez cliquer sur le titre et selectionner le manga
+
+ Sélectionner les données à inclure
+ Sélectionner
+ Migration de source
+ Information complémentaire
+
+
+ Tous
+ Fiable
+ Non fiable
+ Pager
+ Relecture
+ A jour:
+ En cours:
+ Suivant:
+ Précédent:
+ Chapitre suivant introuvable
+ Chapitre précédent introuvable
+ Chargement des pages…
+ Échec du chargement des pages: %1$s
+
+ Appuyer pour sélectionner la source à déplacer
+ Déplacement
+ Copier
+ En cours de déplacement…
+
+ Bleu foncé
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index 0fb316477..583b2c3ab 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -1,7 +1,7 @@
-
+
नाम
श्रेणियाँ
- मंगा
+ मांगा
अध्याय
पदचिह्न
इतिहास
@@ -14,7 +14,7 @@
पुस्तकालय अद्यतन
नवीनतम अद्यतन
श्रेणियाँ
- श्रेणियाँ
+ श्रेणियाँ %1$d
बैकअप
@@ -50,7 +50,6 @@
आवरण चित्र को संपादित करें
ऊपर छांटे
नीचे छांटे
- अपठित
डाउनलोड किया हुआ
अगला अपठित
प्रारंभ
@@ -84,7 +83,7 @@
सर्जन करना
पुनःस्थापन करे
खुला हुआ
- लॉग इन
+ लोगिन
हटाया जा रहा है…
लोड हो रहा है…
@@ -123,7 +122,6 @@
चार्ज होते समय
केवल चालू मंगा का अद्यतन करे
अध्याय पढ़ने के बाद समकालीन करे
- अद्यतन करने से पहले पुष्टि करे
एप्पलीकेशन थीम
मुख्य थीम
गहरी थीम
@@ -180,7 +178,6 @@
डाउनलोड निर्देशिका
- समकालिक डाउनलोड
वाई - फाई पर ही डाउनलोड करे
पढ़ा जाने चिह्नित होने पर हटाएं
पढ़ने के बाद हटा दें
@@ -281,7 +278,6 @@
चल रही है
अज्ञात
लाइसेंस प्राप्त
- पुस्तकालय में जोड़ें
लाइब्रेरी से निकालें
लेखक
कलाकार
@@ -355,8 +351,7 @@
अध्याय %1$s
अगले अध्याय नहीं मिला
पिछला अध्याय नहीं मिला
- चित्र लोड नहीं किया जा सका।
-\nछवि डिकोडर को बदलने या नीचे दिए गए विकल्पों में से एक के साथ आज़माएं
+ छवि को डीकोड नहीं किया जा सका
पिछले अध्याय को सक्षम सेवाओं में %1$d तक पढ़ा है?
क्या आप इस छवि को कवर के रूप में सेट करना चाहते हैं?
इस श्रृंखला के लिए दर्शक
@@ -460,4 +455,24 @@
नकल
प्रवास हो रहा है…
+ पृष्ठक
+ स्रोत स्थापित नहीं है : %1$s
+
+ फिर से पढाना
+ स्थिति
+ शुरू कर दिया है
+ प्रकार
+ लेखक
+ मंगा यूआरएल सेट नहीं है कृपया शीर्षक पर क्लिक करें और फिर मंगा का चयन करें
+
+ समाप्त:
+ प्रचलित:
+ अगला:
+ पिछला:
+ कोई अगला अध्याय नहीं है
+ कोई पिछला अध्याय नहीं है
+ पेज लोड हो रहे है …
+ पृष्ठों को लोड करने में विफल है: %1$s
+
+ गहरा नीला
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 4ac9c7452..f28edbebc 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -1,22 +1,19 @@
-
-Név
+
+
+ Név
Kategóriák
Manga
Fejezetek
Nyilvántartás
Előzmények
-
Beállítások
Legutóbb olvasott
Katalógusok
Legutóbbi frissítések
Kategóriák
Biztonsági mentés
-
-
Beállítások
Keresés
- Olvasatlan
Letöltve
Bezárás
Eltávolítás
@@ -32,10 +29,8 @@
Visszavonás
Exportálás
Bejelentkezés
-
Törlés…
Betöltés…
-
Általános
Olvasó
Letöltések
@@ -43,8 +38,7 @@
Nyilvántartás
Speciális
Névjegy
-
- Könyvtár: mangák száma soronként
+ Könyvtár: mangák száma soronként
Álló
Fekvő
Alapértelmezett
@@ -75,8 +69,7 @@
Rendszer alapértelmezése
Alapértelmezett kategória
Mindig kérdezze
-
- Teljes képernyő
+ Teljes képernyő
Oldalszám megjelenítése
Képernyő bekapcsolva tartása
Lapozás
@@ -95,9 +88,7 @@
Értékelés
Cím
Állapot
-
Beállítások
-
%1$d oldal
%1$s. fejezet
Nem található a következő fejezet
@@ -159,9 +150,7 @@
Megnyitás
Az alkalmazás nem érhető el
Frissítések
-
Olvasás után a fejezetek szinkronizálása
- Megerősítést kér frissítés előtt
Elforgatás zárolása
Áttűnés lapozáskor
Szegélyek vágása
@@ -176,4 +165,9 @@
Függőlegesen
Folytonos
Képernyőhöz igazítás
-
+ Forrás váltása
+ Bővítmények
+ Nem megbízható
+ Eltávolítás
+ Beállítások
+
\ No newline at end of file
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 8af920ba5..cdfa74d5c 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -47,7 +47,6 @@
Ubah gambar sampul
Sortir keatas
Sortir kebawah
- Belum dibaca
Terunduh
Selanjutnya belum dibaca
Mulai
@@ -118,7 +117,6 @@
Pengisian
Hanya perbaharui saja manga yang masih berlanjut
Sinkronkan bab setelah membaca
- Konfirmasikan sebelum memperbarui
Tema aplikasi
Tema utama
Tema gelap
@@ -144,12 +142,12 @@
Warna latar
Putih
Hitam
- Bawaan penampil
+ Penampil bawaan
Bawaan
Kiri ke kanan
Kanan ke kiri
Vertikal
- Kartun web
+ Webtoon
Pengurai gambar
Tipe skala
Pas layar
@@ -175,7 +173,6 @@
Tempat unduhan
- Unduh bersamaan
Hanya unduh melalui Wi-Fi
Hapus ketika ditandai telah dibaca
Hapus setelah dibaca
@@ -270,7 +267,6 @@
Berlanjut
Tidak diketahui
Berlisensi
- Tambahkan ke perpustakaan
Hapus dari perpustakaan
Penulis
Artis
@@ -343,8 +339,7 @@
Bab %1$s
Bab berikutnya tidak ditemukan
Bab sebelumnya tidak ditemukan
- Gambar tidak dapat dimuat.
-\nCoba ganti dekoder gambar atau dengan salah satu opsi di bawah ini
+ Gambar tidak bisa diterjemahkan
Perbaharui bab terakhir baca di layanan yang diaktifkan ke %1$d?
Apakah anda ingin mengatur gambar ini sebagai sampul?
Penonton untuk seri yang ini
@@ -462,4 +457,24 @@
Salin
Memindah…
+ Halaman
+ Sumber tidak terpasang: %1$s
+
+ Baca ulang
+ Status
+ Dimulai
+ Tipe
+ Penulis
+ Url Manga belum siap, silahkan klik judul dan pilih manga lagi
+
+ Biru gelap
+ Selesai:
+ Saat ini:
+ Selanjutnya:
+ Sebelumnya:
+ Tidak ada chapter lanjutannya
+ Tidak ada chapter sebelumnya
+ Memuat halaman…
+ Gagal memuat halaman: %1$s
+
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index ed96b665a..c802af8f1 100755
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -6,7 +6,7 @@
Impostazioni
Coda dei download
La mia libreria
- Recenti
+ Letti di recente
Cataloghi
Aggiornamenti libreria
Ultimi aggiornamenti
@@ -23,13 +23,13 @@
Letti
Elimina filtri
Alfabeticamente
- Ultimo letto
- Ultimo aggiornato
+ Ultimi letti
+ Ultimi aggiornati
Ricerca
Seleziona tutto
- Marca come letto
- Marca come non letto
- Marca precedenti come letti
+ Segna come letto
+ Segna come non letto
+ Segna precedenti come letti
Scarica
Aggiungi segnalibro
Rimuovi segnalibro
@@ -44,10 +44,9 @@
Modifica immagine di copertina
Ordine crescente
Ordine decrescente
- Non letti
Scaricati
Prossimo non letto
- Inizia
+ Avvia
Stop
Pausa
Azzera
@@ -104,7 +103,6 @@
Caricamento
Aggiorna solo manga in corso
Sincronizza capitoli dopo la lettura
- Chiedi prima di aggiornare
Tema dell\'applicazione
Tema principale
Tema scuro
@@ -113,20 +111,20 @@
Predefinita di sistema
- Schermo intero
+ Schermo Intero
Blocca orientamento
- Abilita transizioni
- Mostra numero di pagina
+ Transazioni pagine
+ Mostra numero pagina
Usa luminosità personalizzata
Usa filtro colore personalizzato
- Mantieni lo schermo acceso
+ Mantieni schermo acceso
Navigazione
Tasti volume
- Singolo tocco
- Colore di sfondo
+ Tocco singolo
+ Colore sfondo
Bianco
Nero
- Visualizzatore predefinito
+ Ordine di lettura
Predefinito
Da sinistra a destra
Da destra a sinistra
@@ -136,20 +134,20 @@
Scala
Adatta a schermo
Riempi schermo
- Adatta alla larghezza
- Adatta all\'altezza
- Grandezza originale
- Adattamento intelligente
- Posizione zoom iniziale
+ Adatta a larghezza
+ Adatta a altezza
+ Originale
+ Scala intelligente
+ Posizione inizio pagine zoom
Automatico
Sinistra
Destra
Centro
- Rotazione
- Libera
- Bloccata
- Forza verticale
- Forza orizzontale
+ Orientamento
+ Libero
+ Bloccato
+ Blocca verticale
+ Blocca orizzontale
R
G
B
@@ -157,12 +155,11 @@
- Directory di download
- Download simultanei
+ Cartella download
Download solo tramite Wi-Fi
- Rimuovi quando marcato come letto
- Rimuovi dopo la lettura
- Directory personalizzata
+ Cancella quando segnato come letto
+ Cancella dopo lettura
+ Cartella personalizzata
Disabilitato
Ultimo capitolo letto
Penultimo capitolo
@@ -181,30 +178,30 @@
Si è verificato un errore durante la cancellazione della cache
Cancella cookie
Cookie cancellati
- Cancella database
+ Pulisci database
Elimina manga e capitoli che non sono nella tua libreria
- Sei sicuro? Capitoli e progressi dei manga non presenti nella libreria verranno persi
+ Sei sicuro? I capitoli letti e i progressi di lettura di elementi fuori dalla libreria verranno persi
Elementi eliminati
- Aggiorna i metadati della libreria
+ Aggiorna metadati libreria
Aggiorna copertine, generi, descrizioni e stato dei manga
Versione
- Data di build
- Controlla aggiornamenti
- Controlla automaticamente aggiornamenti dell\'applicazione
+ Data della build
+ Controllo aggiornamenti automatico
+ Controllo aggiornamenti automatici
- Invia segnalazioni di crash
+ Invia segnalazioni
Aiuta a correggere eventuali bug. Non verranno inviati dati sensibili
- Credenziali per %1$s
+ Login per %1$s
Nome utente
Password
Mostra password
Accedi
- Accesso riuscito
+ Accesso completato
Credenziali non valide
Errore sconosciuto
@@ -227,7 +224,6 @@
In corso
Sconosciuto
Concesso in licenza
- Aggiungi alla libreria
Rimuovi dalla libreria
Autore
Artista
@@ -300,7 +296,7 @@
Capitolo %1$s
Capitolo successivo non trovato
Capitolo precedente non trovato
- L\'immagine non può essere caricata.\nProva a cambiare il decoder immagini o con una delle opzioni più in basso
+ L\'immagine non può essere decodificata
Aggiornare nei servizi abilitati l\'ultimo capitolo letto a %1$d?
Vuoi impostare questa immagine come copertina?
Visualizzatore per questa serie
@@ -352,7 +348,7 @@
Nessun download
Non ci sono capitoli più recenti
Non ci sono manga letti di recente
- Libreria vuota
+ Libreria vuota, aggiungi una serie dai Cataloghi.
Downloader
@@ -388,37 +384,37 @@
Monitoraggio
Chiedi sempre
- Ritaglia i bordi
+ Ritaglia bordi
Inverti i tasti del volume
Categorie da includere nel download
Crea backup
- Può essere usato per ripristinare la libreria attuale
+ Potrà essere utilizzato per ripristinare la libreria attuale
Ripristina backup
Ripristina la libreria da un file di backup
Cartella di backup
- Frequenza di backup
+ Frequenza tra backup
Backup automatici massimi
Backup creato
Ripristino completato
Impossibile aprire log
Servizio
- Ripristino backup
-%1$s aggiunto alla libreria
+ Ripristino backup
+\n%1$s aggiunto alla libreria
Sorgente non trovata
- Ripristino backup
-%1$s sorgente non trovata
- Il ripristino ha impiegato %1$s.
-%2$s errori.
- Il ripristino usa la sorgente per recuperare i dati, potrebbero essere applicati costi dell\'operatore.
-Prima del ripristino assicurati anche di avere effettuato l\'accesso per le sorgenti che lo richiedono.
- File salvato in %1$s
- Di cosa vuoi effettuare il backup?
+ Ripristino backup
+\n%1$s sorgente non trovata
+ Il ripristino ha impiegato %1$s.
+\nriscontrando %2$s errori.
+ Il ripristino usa le tue sorgenti per recuperare i dati, costi potrebbero variare in base alla tua offerta dati.
+\nAssicurati di aver installato le sorgenti corrette prima di ripristinare.
+ File salvato su %1$s
+ Cosa vuoi salvare?
Ripristino backup
Creazione backup
- Ricarica metadati di monitoraggio
- Aggiorna stato, punteggio e ultimo capitolo letto a partire dai servizi di monotoraggio
+ Ricarica metadati progresso
+ Aggiorna stato, voto e ultimo capitolo letto dalla funzione segna progresso
Elimina anche i capitoli scaricati
@@ -435,4 +431,97 @@ Prima del ripristino assicurati anche di avere effettuato l\'accesso per le sorg
Download in pausa
-
+Migrazione fonti
+ Estensioni
+ Info estensioni
+
+
+ Ricerca globale
+ Indicatori capitoli scaricati
+ Reimposta
+ Apri
+ Accesso
+
+ Tutto
+ Dettagli
+ Aggiorna
+ Installa
+ In coda
+ Download in corso
+ Installazione in corso
+ Installato
+ Mi fido
+ Non affidabile
+ Disinstalla
+ Preferenze
+ Disponibile
+ Estensione non affidabile
+ Questa estensione è stata firmata con un certificato non affidabile e non è stata attivata.
+\n
+\nUn\'estensione pericolosa potrebbe leggere credenziali di accesso all\'interno di Tachiyomi or eseguire codice dannoso.
+\n
+\nContinuando con questo certificato accetti questi rischi.
+ Versione: %1$s
+ Lingua: %1$s
+ Questa estensione non ha preferenze
+
+ Velocità animazioni doppio tocco
+ Pagine
+ Disattivato
+ Normale
+ Veloce
+ Ripristino scelte dialogo
+ Locale
+ Filtri ricerca
+ Altro
+ Predefinito non può essere selezionato con altre categorie
+ Ricerca globale…
+ Nessun risultato!
+ Ultimi
+ Sfoglia
+
+ Titolo
+ Aggiunto alla libreria
+ Rimosso dalla libreria
+ Aggiornato
+ Collegamento aggiunto alla home.
+ %1$s copiato negli appunti
+ Sorgente non installata: %1$s
+
+ Scarica quantità personalizzata
+ quantità
+ Download personalizzato
+ Ri-leggendo
+ Stato
+ Iniziato
+ Tipo
+ Autore
+ L\'URL del manga non è impostato clicca sul titolo e seleziona nuovamente il manga
+
+ Categorie rimosse
+
+ Tocca per selezionare la sorgente dalla quale migrare
+ Seleziona i dati da includere
+ Seleziona
+ Migrare
+ Copia
+ Migrazione…
+
+ Per %1$d titoli
+ Non hai categorie. Aggiungine una per creare e organizzare la tua libreria.
+
+ Comune
+ Libreria
+ Downloader
+
+Finito:
+ Corrente:
+ Prossimo:
+ Precedente:
+ Nessun capitolo successivo
+ Nessun capitolo precedente
+ Caricamento pagine…
+ Caricamento pagine fallito: %1$s
+
+ Blu scuro
+
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 9fcb046c8..ce02b5613 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -1,4 +1,4 @@
-
+
名
カテゴリ
漫画
@@ -37,4 +37,176 @@
グリッド
リスト
ソート
-
+ 追跡
+ 拡張機能
+ 拡張機能の情報
+ ソース移行
+ 既読にする
+ 未読としてマーク
+ 削除する
+ 更新
+ ライブラリを更新する
+ 編集
+ カテゴリを追加
+ カテゴリを編集
+ 表紙画像を編集
+ 開始
+ 一時停止
+ クリア
+ クリア
+ 前の章
+ 次の章
+ リトライ
+ 履歴書
+ ブラウザで開く
+ ホーム画面に追加
+ 表示モードを変更する
+ 表示
+ バッジをダウンロードする
+ キャンセル
+ シェア
+ 保存する
+ リセット
+ 輸出する
+ ログを開く
+ 作成する
+ リストア
+ 開いた
+ ログイン
+ 削除しています…
+ 読み込み中…
+ 利用できないアプリケーション
+ 全般
+ ダウンロード
+ 出典
+ 高度な
+ 約
+ ポートレート
+ 景観
+ デフォルト
+ ライブラリ更新頻度
+ マニュアル
+ 毎時
+ 2時間ごと
+ 3時間ごと
+ 6時間ごと
+ 12時間ごと
+ 毎日
+ 2日ごと
+ 毎週
+ 毎月
+ グローバルアップデートに含まれるカテゴリ
+ すべて
+ ライブラリ更新制限
+ 条件が満たされた場合にのみ更新する
+ Wi-Fi
+ 充電
+ 進行中の漫画のみ更新
+ 読んだ後に章を同期
+ アプリケーションテーマ
+ メインテーマ
+ ダークテーマ
+ AMOLEDテーマ
+ ダークブルー
+ スタート画面
+ 言語
+ システム標準
+ デフォルトカテゴリ
+ 毎回尋ねる
+ すべて
+ 詳細
+ 更新
+ インストール
+ 保留
+ ダウンロード中
+ インストール
+ インストール済み
+ トラスト
+ 信用できない
+ アンインストール
+ 設定
+ 利用可能
+ 信頼できない拡張
+ バージョン:%1$s
+ 言語: %1$s
+ フルスクリーン
+ ページ遷移
+ ダブルタップアニメーション速度
+ クロップボーダー
+ カスタムの明るさを使う
+ カスタムカラーフィルターを使用
+ ナビゲーション
+ ボリュームキー
+ 音量キーを反転する
+ タッピング
+ 背景色
+ 白
+ ブラック
+ デフォルトビューア
+ デフォルト
+ 左から右
+ 右から左
+ 縦反転
+ Webtoon
+ ページャ
+ 画像デコーダ
+ スケールの種類
+ フィット画面
+ ストレッチ
+ 幅に合わせる
+ オリジナルサイズ
+ スマートフィット
+ ズーム開始位置
+ 自動
+ 左
+ 右
+ 中央
+ 通常
+ 速い
+ 回転
+ 無料
+ ロック
+ 強制肖像画
+ R
+ ジー
+ B
+ A
+ ダウンロードディレクトリ
+ Wi-Fi経由でのみダウンロード
+ 既読としてマークされている場合は削除
+ 読んだ後に削除
+ カスタムディレクトリ
+ 無効
+ 最後の章の2番目
+ 新しい章をダウンロードする
+ ダウンロードに含まれるカテゴリ
+ バックアップ
+ バックアップを作成する
+ 現在のライブラリを復元するために使用できます。
+ バックアップを復元
+ バックアップファイルからライブラリを復元する
+ バックアップディレクトリ
+ サービス
+ バックアップ頻度
+ 最大自動バックアップ
+ バックアップを復元します
+\n%1$s がライブラリに追加されました
+ ソースが見つかりません
+ バックアップが作成されました
+ ログをオープンできませんでした
+ チャプタキャッシュをクリア
+ キャッシュの消去中にエラーが発生しました
+ データベースをクリア
+ 図書館にない漫画や章を削除する
+ バージョン
+ ビルドタイム
+ アップデートをチェック
+ アプリケーションのアップデートを自動的に確認する
+ クラッシュレポートを送信する
+ %1$s のためにログイン
+ ユーザー名
+ パスワード
+ パスワードを表示
+ ログイン
+ ログイン成功
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 8e9cc7abf..d1961d337 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -37,7 +37,6 @@
카테고리 이름 다시 짓기
카테고리로 이동
표지 이미지 바꾸기
- 아직 읽지 않음
다운로드됨
정지
제거
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index 6d878aec4..36a8d6687 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -48,7 +48,6 @@
Mainīt vāka attēlu
Kārtot augšup
Kārtot lejup
- Neizlasītie
Lejupieladēti
Nākamais neizlasīts
Sākt
diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml
index ee1b8e5da..401c91d9a 100644
--- a/app/src/main/res/values-ms/strings.xml
+++ b/app/src/main/res/values-ms/strings.xml
@@ -50,7 +50,6 @@
Ubah muka hadapan
Susunan menaik
Susunan menurun
- Belum dibaca
Telah dimuat turun
Belum dibaca seterusnya
Mula
@@ -122,7 +121,6 @@
Ketika mengecas
Hanya kemas kini manga yang masih berterusan
Sinkronkan bab setelah dibaca
- Minta pengesahan sebelum kemas kini
Tema aplikasi
Tema utama
Tema gelap
@@ -181,7 +179,6 @@
Direktori muat turun
- Muat turun serentak
Hanya muat turun melalui Wi-Fi
Padam apabila ditandakan sebagai dibaca
Padam setelah dibaca
@@ -282,7 +279,6 @@
Berterusan
"Tidak diketahui "
Berlesen
- Tambahkan kepada koleksi
Keluarkan daripada koleksi
Pengarang
Artis
@@ -414,4 +410,5 @@
Biasa
Koleksi
Pemuat turun
-
+Pindah sumber
+
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
new file mode 100644
index 000000000..1d7cafbf5
--- /dev/null
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -0,0 +1,139 @@
+
+Navn
+ Kategorier
+ Manga
+ Kapittel
+ Historikk
+ Innstillinger
+ Nedlastingskø
+ Mitt bibliotek
+ Nylig lest
+ Kateloger
+ Bibliotekoppdateringer
+ Nyeste oppdateringer
+ Kategorier
+ Valgt: %1$d
+ Sikkerhetskopi
+ Kildeflytting
+ Utvidelser
+ Utvidelsesinfo
+ Innstillinger
+ Filter
+ Nedlastet
+ Bokmerket
+ Ulest
+ Les
+ Fjern filter
+ Alfabetisk
+ Totale kapittel
+ Sist lest
+ Sist oppdatert
+ Søk
+ Velg alt
+ Marker som lest
+ Marker som ulest
+ Merk forrige som lest
+ Last ned
+ Bokmerk
+ Fjern bokmerke
+ Slett
+ Oppdater
+ Oppdater bibliotek
+ Rediger
+ Legg til
+ Legg til kategori
+ Rediger kategorier
+ Gi kategori nytt navn
+ Flytt til kategorier
+ Rediger omslagsbildet
+ Sorter oppover
+ Sorter nedover
+ Nedlastet
+ Neste uleste
+ Start
+ Stopp
+ Pause
+ Tøm
+ Lukk
+ Forrige kapittel
+ Neste kapittel
+ Prøv igjen
+ Fjern
+ Fortsett
+ Flytt
+ Åpne i nettleser
+ Legg til på hjemmeskjerm
+ Endre visningsmodus
+ Vis
+ Rutenett
+ Liste
+ Sett filter
+ Avbryt
+ Sorter
+ Installer
+ Del
+ Lagre
+ Tilbakestill
+ Angre
+ Eksporter
+ Åpne logg
+ Opprett
+ Gjenopprett
+ Åpne
+ Logg inn
+ Sletter…
+ Laster…
+ Program ikke tilgjengelig
+ Oppdateringer
+ Generelt
+ Leser
+ Nedlastinger
+ Kilder
+ Avansert
+ Om
+ Forvalg
+ Manuell
+ Timevis
+ Hver andre time
+ Hver tredje time
+ Hver sjette time
+ Hver tolvte time
+ Daglig
+ Annenhver dag
+ Ukentlig
+ Månedlig
+ Alle
+ Wi-Fi
+ Lading
+ Hoveddrakt
+ Mørk drakt
+ AMOLED-drakt
+ Mørkeblå
+ Startskjerm
+ Språk
+ Systemforvalg
+ Forvalgt kategori
+ Alltid spør
+ Alle
+ Detaljer
+ Oppdater
+ Installer
+ Ventende
+ Laster ned
+ Installerer
+ Installert
+ Tillit
+ Ubetrodd
+ Avinstaller
+ Innstillinger
+ Tilgjengelig
+ Ubetrodd utvidelse
+ Versjon: %1$s
+ Språk: %1$s
+ Fullskjermsvisning
+ Lås sideretning
+ Sideoverganger
+ Vis sidenummer
+ Behold skjerm på
+ Navigasjon
+
\ No newline at end of file
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index dae9a9986..4f8692b35 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -1,11 +1,11 @@
-
-Naam
+
+
+ Naam
Categorieën
Manga
Hoofdstukken
Tracking
Geschiedenis
-
Instellingen
Mijn bibliotheek
Onlangs gelezen
@@ -14,7 +14,6 @@
Categorieën
Geselecteerd: %1$d
Back-up
-
Instellingen
Filter
Gedownload
@@ -41,7 +40,6 @@
Categorie hernoemen
Verplaatsen naar categorieën
Omslag wijzigen
- Ongelezen
Gedownload
Volgende ongelezen
Start
@@ -74,10 +72,8 @@
Open log
Maken
Terugzetten
-
Verwijderen…
Laden…
-
Algemeen
Lezer
Downloads
@@ -105,8 +101,7 @@
Systeemstandaard
Standaard categorie
Altijd vragen
-
- Volledig scherm
+ Volledig scherm
Paginaovergangen
Toon paginanummer
Randen bijsnijden
@@ -139,10 +134,7 @@
G
B
A
-
-
- Downloadmap
- Gelijktijdige downloads
+ Downloadmap
Download alleen over Wi-Fi
Aangepaste map
Laatst gelezen hoofdstuk
@@ -150,7 +142,6 @@
Op 2 na laaste
Download nieuwe hoofdstukken
Services
-
Back-up
Back-up maken
Kan worden gebruikt om huidige bibliotheek terug te zetten
@@ -174,13 +165,11 @@
Wat wil je back-uppen?
Backup aan het terugzetten
Back-up aan het maken
-
- Gebruikt: %1$s
+ Gebruikt: %1$s
Verwijder cookies
Cookies verwijdered
Update status, score en laatst gelezen hoofdstuk van de tracking services
-
- Versie
+ Versie
Op updates controleren
Automatisch op updates controleren
Login voor %1$s
@@ -189,25 +178,20 @@
Toon wachtwoord
Log in
Onbekende fout
-
Titel of auteur…
Categorie bijwerken
Ben je zeker dat je de geselecteerde manga wil verwijderen?
Verwijder ook gedownloade hoofdstukken
-
Selecteer een bron
Gelieve zeker één geldige bron te kiezen
Geen verdere resultaten
Lokale manga
Standaard kan niet geselecteerd worden met andere categorieën
De manga is toegevoegd aan je bibliotheek
-
Deze manga is verwijderd uit de databank!
-
Info
Beschrijving
Onbekend
- Toevoegen aan bibliotheek
Verwijderen uit bibliotheek
Auteur
Artiest
@@ -234,24 +218,18 @@
Download alles
Download ongelezen
Ben je zeker dat je de geselecteerde hoofdstukken wilt verwijderen?
-
Tracking
Voltooid
Score
Titel
Status
-
Er bestaat al een categorie met deze naam!
Categorieën verwijderd
-
Reset alle hoofdstukken voor deze manga
-
Manga toevoegen aan bibliotheek?
-
Afbeelding opgeslagen
Afbeelding aan het opslaan
Opties
-
Aangepaste filter
Instellen als omslagfoto
Omslagfoto bijgewerkt
@@ -264,9 +242,7 @@
Vorige hoofdstuk niet gevonden
Wil je deze afbeelding als omslagfoto gebruiken?
Lezer voor deze serie
-
%1$s - Hfdst.%2$s
-
Updatevoortgang: %1$d/%2$d
Nieuwe hoofdstukken gevonden
Voor %1$d titels
@@ -280,36 +256,29 @@
Download gestart
Download voltooid
Update beschikbaar
-
Omslag van de manga
-
Geen downloads
Geen recente hoofdstukken
De bibliotheek is leeg, manga kunnen toegevoegd worden vanuit de catalogi.
Er zijn nog geen categorieën, druk op de plus knop om een categorie aan te maken.
-
Downloader
Error
Een pagina is niet geladen
Geen Wi-Fi verbinding beschikbaar
Geen netwerkverbinding beschikbaar
Download gepauzeerd
-
-Bibliotheek updates
+ Bibliotheek updates
Aantal hoofdstukken
Sorteer oplopend
Sorteer aflopend
Applicatie niet beschikbaar
Updates
-
Over
-
- Staand
+ Staand
Liggend
Update alleen wanneer de voorwaarden zijn voldaan
Wi-Fi
Aan het opladen
- Vraag om bevestiging alvorens te updaten
Thema
Hoofdthema
Navigatie
@@ -336,8 +305,6 @@
Compilatietijd
Rapporteer bugs
Helpt bij het bestrijden van bugs. Geen gevoelige data wordt verzonden
-
-
Geen recentelijk gelezen manga
Er is een onverwachte fout opgetreden bij het downloaden van het hoofdstuk
Er ontbreekt een pagina
@@ -349,8 +316,7 @@
Vergrendel oriëntatie
Automatisch dimmen uitschakelen
Categorieën om op te nemen in de download
-
- Ben je zeker? Gelezen hoofdstukken en voortgang voor manga niet in de bibliotheek zullen verloren gaan
+ Ben je zeker? Gelezen hoofdstukken en voortgang voor manga niet in de bibliotheek zullen verloren gaan
Databank geleegd
Deze bron vereist dat je inlogt
Lopend
@@ -362,7 +328,6 @@
Icoonvorm
Maken van snelkoppeling mislukt!
Verwijder gedownloade hoofdstukken?
-
Hoofdstukken
Geen titel
Er is een fout opgetreden bij het ophalen van hoofdstukken
@@ -373,17 +338,13 @@
Afbeelding kon niet worden geladen.
Probeer de afbeeldingdecoder te veranderen met een van de onderstaande opties
Er is een fout opgetreden bij het downloaden van hoofdstukken. Je kan het opnieuw proberen in de downloadwachtrij
-
Updaten van omslag mislukt
Synchronisatie geannuleerd
Niet aangesloten op netstroom
Synchronisatie geannuleerd
Verbinding niet beschikbaar
-
Selecteer icoon voor snelkoppeling
-
Aan het controleren op updates
-
Download update
Bezig met downloaden
Fout bij het downloaden
@@ -402,7 +363,6 @@ Zorg ook dat je ingelogd bent voor bronnen die dit vereisen alvorens je het teru
Download badges
Open
Inloggen
-
Lokaal
Alternatief
Globaal zoeken…
@@ -411,7 +371,11 @@ Zorg ook dat je ingelogd bent voor bronnen die dit vereisen alvorens je het teru
Snelkoppeling toegevoegd aan startscherm.
Bibliotheek
Downloader
-Bladeren
-
+ Bladeren
Algemeen
-
+ Bronmigratie
+ Extensies
+ Informatie extensie
+ hoeveelheid
+ Donkerblauw
+
\ No newline at end of file
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index a729975be..1b01f0e64 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -1,4 +1,4 @@
-
+
Kategorie
Rozdziały
Nazwa
@@ -43,7 +43,6 @@
Ilość rozdziałów
Sortuj rosnąco
Sortuj malejąco
- Nieprzeczytane
Pobrane
Następne nieprzeczytane
Start
@@ -89,7 +88,6 @@
Ilość rzędów biblioteki
Aktualizuje okładki, gatunki, opisy i informacje o statusie mang
- Dodaj do biblioteki
Usuń z biblioteki
Autor
Rysownik
@@ -214,7 +212,6 @@
Ładowanie baterii
Aktualizuj tylko nieukończone mangi
Aktualizuj postęp po przeczytaniu rozdziału
- Potwierdź przed aktualizacją
Motyw aplikacji
Główny motyw
Ciemny motyw
@@ -270,7 +267,6 @@
Folder pobierania
- Liczba równoczesnych pobierań
Pobieraj tylko przez Wi-Fi
Usuwaj po oznaczeniu jako przeczytane
Usuwaj rozdziały po przeczytaniu
@@ -371,8 +367,7 @@ Nie znaleziono źródła %1$s
Filtr niestandardowy
Strona skopiowana do %1$s
- Nie można załadować obrazka.
-Spróbuj zmienić dekoder lub skorzystaj z jednej z opcji poniżej
+ Nie udało się wczytać obrazka
Zaktualizować ostatni przeczytany rozdział na %1$d?
Czy chcesz ustawić ten obrazek jako okładkę?
Widok dla tej serii
@@ -410,7 +405,7 @@ Spróbuj zmienić dekoder lub skorzystaj z jednej z opcji poniżej
Zwykły
Biblioteka
Ostatnie
- Menadżer pobierania
+ Menedżer pobierania
Plakietki pobrań
Lokalna
Nie masz żadnych kategorii. Dotknij plusa, aby je utworzyć i zorganizować swoją bibliotekę.
@@ -444,15 +439,15 @@ Spróbuj zmienić dekoder lub skorzystaj z jednej z opcji poniżej
Aktualizuj
Ufaj
- Nie zaufane
+ Niezaufane
Preferencje
Dostępne
- Nie zaufane rozszerzenie
- To rozszerzenie było podpisane nie zaufanym certyfikatem i nie zostało aktywowane.
-\n
-\nZłośliwe rozszerzenie może odczytać dane logowania przechowywane w tachiyomi albo uruchomić złośliwy kod.
-\n
-\nPoprzez zaufanie temu rozszerzeniu potwierdzasz że rozumiesz to zagrożenie.
+ Niezaufane rozszerzenie
+ To rozszerzenie było podpisane niezaufanym certyfikatem i nie zostało aktywowane.
+\n
+\nZłośliwe rozszerzenie może odczytać dane logowania przechowywane w Tachiyomi albo uruchomić złośliwy kod.
+\n
+\nPoprzez zaufanie temu rozszerzeniu potwierdzasz, że rozumiesz to zagrożenie.
To rozszerzenie nie ma żadnych preferencji do edycji
Filtry wyszukiwania
@@ -464,4 +459,23 @@ Spróbuj zmienić dekoder lub skorzystaj z jednej z opcji poniżej
Migruj
Migrowanie…
-
+ Widok stron
+ Nie zainstalowano źródła %1$s
+
+ Czytane ponownie
+ Status
+ Rozpoczęto
+ Typ
+ Autor
+ URL mangi nie jest ustawiony, kliknij tytuł i wybierz mangę jeszcze raz
+
+ Przeczytany:
+ Obecny:
+ Następny:
+ Poprzedni:
+ Brak następnego rozdziału
+ Nie ma wcześniejszych rozdziałów
+ Ładowanie stron…
+ Nie udało się załadować stron: %1$s
+ Ciemny niebieski
+
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index edf436ff2..66f5a6c45 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -46,9 +46,8 @@
Renomear categoria
Mover para categorias
Alterar imagem da capa
- Ordem ascendente
+ Ordem crescente
Ordem decrescente
- Não lido
Disponível offline
Próximo não lido
Iniciar
@@ -106,10 +105,10 @@
A cada 3 horas
A cada 6 horas
A cada 12 horas
- A cada dia
+ Diariamente
A cada 2 dias
- A cada semana
- A cada mês
+ Semanalmente
+ Mensalmente
Categorias inclusas na atualização global
Todas
Restrições da atualização global
@@ -118,7 +117,6 @@
Carregando
Atualizar apenas mangás em andamento
Sincronizar capítulos após leitura
- Confirmar antes de atualizar
Tema do aplicativo
Tema principal
Tema escuro
@@ -131,12 +129,12 @@
Tela cheia
Bloquear orientação
- Transições de páginas
+ Transição de página
Mostrar número da página
Aparar bordas
Usar brilho personalizado
Usar filtro de cores personalizado
- Manter tela ligada
+ Manter a tela ligada
Navegação
Botões de volume
Inverter botões de volume
@@ -144,7 +142,7 @@
Cor de fundo
Branco
Preto
- Visualização padrão
+ Sentido de leitura padrão
Padrão
Esquerda para direita
Direita para esquerda
@@ -175,7 +173,6 @@
Pasta de downloads
- Downloads simultâneos
Fazer download apenas via Wi-Fi
Excluir ao marcar como lido
Excluir depois de ler
@@ -258,7 +255,7 @@ Além disso, verifique se as fontes que requerem uma conta foram configuradas co
Esta fonte requer uma conta
Selecione uma fonte
Por favor, ative pelo menos uma fonte válida
- Mais nenhum resultado
+ Não há mais resultados
Mangá local
O mangá foi adicionado à sua biblioteca
@@ -269,7 +266,6 @@ Além disso, verifique se as fontes que requerem uma conta foram configuradas co
Em andamento
Desconhecido
Licenciado
- Adicionar à biblioteca
Remover da biblioteca
Autor
Artista
@@ -305,7 +301,7 @@ Além disso, verifique se as fontes que requerem uma conta foram configuradas co
Fazer download
Fazer download do próximo capítulo
Fazer download dos próximos 5 capítulos
- Fazer download dos próximos 10 capítulo
+ Fazer download dos próximos 10 capítulos
Fazer download de tudo
Fazer download dos não lidos
Tem certeza de que deseja excluir os capítulos selecionados?
@@ -342,11 +338,10 @@ Além disso, verifique se as fontes que requerem uma conta foram configuradas co
Capítulo %1$s
Próximo capítulo não encontrado
Capítulo anterior não encontrado
- A imagem não pôde ser carregada.
-Tente alterar o decodificador de imagens ou selecione uma das opções abaixo
- Atualizar o último capítulo de %1$d lido nos serviços ativos?
+ A imagem não pôde ser decodificada
+ Atualizar o último capítulo lido para %1$d nos serviços ativos?
Deseja usar esta imagem como capa?
- Visualizador para está série
+ Leitura para esta série
%1$s - Cap.%2$s
@@ -358,7 +353,7 @@ Tente alterar o decodificador de imagens ou selecione uma das opções abaixoErro ao atualizar a capa
Por favor, adicione o mangá à sua biblioteca antes de fazer isso
Sincronização cancelada
- O dispositivo não está conectado à energia
+ O dispositivo não está sendo carregado
Sincronização cancelada
Conexão indisponível
@@ -390,7 +385,7 @@ Tente alterar o decodificador de imagens ou selecione uma das opções abaixoGerenciador de downloads
Erro
Ocorreu um erro inesperado ao fazer download do capítulo
- Página em falta na pasta
+ Uma página está faltando na pasta
Página não carregada
Nenhuma conexão Wi-Fi disponível
Conexão à rede indisponível
@@ -411,9 +406,9 @@ Tente alterar o decodificador de imagens ou selecione uma das opções abaixoComum