diff --git a/app/src/main/java/com/hippo/drawable/BatteryDrawable.java b/app/src/main/java/com/hippo/drawable/BatteryDrawable.java new file mode 100644 index 000000000..85ea34ebc --- /dev/null +++ b/app/src/main/java/com/hippo/drawable/BatteryDrawable.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.drawable; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +import com.hippo.yorozuya.MathUtils; + +public class BatteryDrawable extends Drawable { + + @SuppressWarnings("unused") + private static final String TAG = BatteryDrawable.class.getSimpleName(); + + public static final int WARN_LIMIT = 15; + + private int mColor = Color.WHITE; + private int mWarningColor = Color.RED; + private int mElect = -1; + private final Paint mPaint; + + private final Rect mTopRect; + private final Rect mBottomRect; + private final Rect mRightRect; + private final Rect mHeadRect; + private final Rect mElectRect; + private int mStart; + private int mStop; + + public BatteryDrawable() { + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + mPaint.setStyle(Paint.Style.FILL); + + mTopRect = new Rect(); + mBottomRect = new Rect(); + mRightRect = new Rect(); + mHeadRect = new Rect(); + mElectRect = new Rect(); + updatePaint(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + int width = bounds.width(); + int height = bounds.height(); + int strokeWidth = (int) (Math.sqrt(width * width + height * height) * 0.06f); + int turn1 = width * 6 / 7; + int turn2 = height / 3; + int secBottom = height - strokeWidth; + mStart = strokeWidth; + mStop = turn1 - strokeWidth; + + mTopRect.set(0, 0, turn1, strokeWidth); + mBottomRect.set(0, secBottom, turn1, height); + mRightRect.set(turn1 - strokeWidth, strokeWidth, turn1, secBottom); + mHeadRect.set(turn1, turn2, width, height - turn2); + mElectRect.set(0, strokeWidth, mStop, secBottom); + } + + /** + * How to draw:
+ * |------------------------------|
+ * |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|
+ * |------------------------------|---|
+ * |/////////////////| |//|\\\|
+ * |/////////////////| |//|\\\|
+ * |------------------------------|---|
+ * |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|
+ * |------------------------------| + */ + @Override + public void draw(Canvas canvas) { + if (mElect == -1) + return; + + mElectRect.right = MathUtils.lerp(mStart, mStop, mElect / 100.0f); + + canvas.drawRect(mTopRect, mPaint); + canvas.drawRect(mBottomRect, mPaint); + canvas.drawRect(mRightRect, mPaint); + canvas.drawRect(mHeadRect, mPaint); + canvas.drawRect(mElectRect, mPaint); + } + + private boolean isWarn() { + return mElect <= WARN_LIMIT; + } + + public void setColor(int color) { + if (mColor == color) { + return; + } + + mColor = color; + if (!isWarn()) { + mPaint.setColor(mColor); + invalidateSelf(); + } + } + + public void setWarningColor(int color) { + if (mWarningColor == color) { + return; + } + + mWarningColor = color; + if (isWarn()) { + mPaint.setColor(mWarningColor); + invalidateSelf(); + } + } + + public void setElect(int elect) { + if (mElect == elect) { + return; + } + + mElect = elect; + updatePaint(); + } + + public void setElect(int elect, boolean warn) { + if (mElect == elect) { + return; + } + + mElect = elect; + updatePaint(warn); + } + + private void updatePaint() { + updatePaint(isWarn()); + } + + private void updatePaint(boolean warn) { + if (warn) + mPaint.setColor(mWarningColor); + else + mPaint.setColor(mColor); + invalidateSelf(); + } + + @Override + public void setAlpha(int alpha) { + // Empty + } + + @Override + public void setColorFilter(ColorFilter cf) { + // Empty + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.java b/app/src/main/java/com/hippo/ehviewer/Settings.java index e980b47d6..a14562c4a 100644 --- a/app/src/main/java/com/hippo/ehviewer/Settings.java +++ b/app/src/main/java/com/hippo/ehviewer/Settings.java @@ -119,6 +119,9 @@ public class Settings { return sEhConfig; } + /******************** + ****** Eh + ********************/ private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title"; private static final boolean DEFAULT_SHOW_JPN_TITLE = false; @@ -126,6 +129,16 @@ public class Settings { return getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE); } + /******************** + ****** Read + ********************/ + private static final String KEY_VOLUME_PAGE = "volume_page"; + private static final boolean DEFAULT_VOLUME_PAGE = false; + + public static boolean getVolumePage() { + return getBoolean(KEY_VOLUME_PAGE, DEFAULT_VOLUME_PAGE); + } + public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme"; public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority"; public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path"; diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java index f24a99a09..e6eab8fb2 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java @@ -22,6 +22,7 @@ import android.support.annotation.Nullable; import com.hippo.ehviewer.client.data.GalleryInfo; import com.hippo.ehviewer.spider.SpiderQueen; import com.hippo.image.Image; +import com.hippo.yorozuya.SimpleHandler; public class EhGalleryProvider extends GalleryProvider implements SpiderQueen.OnSpiderListener { @@ -49,7 +50,15 @@ public class EhGalleryProvider extends GalleryProvider implements SpiderQueen.On if (mSpiderQueen != null) { mSpiderQueen.removeOnSpiderListener(this); - SpiderQueen.releaseSpiderQueen(mSpiderQueen, SpiderQueen.MODE_READ); + final SpiderQueen spiderQueen = mSpiderQueen; + mSpiderQueen = null; + // Activity recreate may called + SimpleHandler.getInstance().postDelayed(new Runnable() { + @Override + public void run() { + SpiderQueen.releaseSpiderQueen(spiderQueen, SpiderQueen.MODE_READ); + } + }, 3000); } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java index d83f3c896..db7181668 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java @@ -22,8 +22,8 @@ import android.support.annotation.UiThread; import com.hippo.gl.glrenderer.GLCanvas; import com.hippo.gl.view.GLRoot; import com.hippo.image.Image; +import com.hippo.yorozuya.ConcurrentPool; import com.hippo.yorozuya.OSUtils; -import com.hippo.yorozuya.Pool; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -41,7 +41,7 @@ public abstract class GalleryProvider { ".gif", // Graphics Interchange Format }; - private final Pool mNotifyTaskPool = new Pool<>(5); + private final ConcurrentPool mNotifyTaskPool = new ConcurrentPool<>(5); private volatile GalleryProviderListener mGalleryProviderListener; private volatile GLRoot mGLRoot; @@ -130,8 +130,8 @@ public abstract class GalleryProvider { public static final int TYPE_SUCCEED = 2; public static final int TYPE_FAILED = 3; - private GalleryProviderListener mGalleryProviderListener; - private Pool mPool; + private final GalleryProviderListener mGalleryProviderListener; + private final ConcurrentPool mPool; @Type private int mType; @@ -140,7 +140,7 @@ public abstract class GalleryProvider { private Image mImage; private String mError; - public NotifyTask(GalleryProviderListener galleryProviderListener, Pool pool) { + public NotifyTask(GalleryProviderListener galleryProviderListener, ConcurrentPool pool) { mGalleryProviderListener = galleryProviderListener; mPool = pool; } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryPageView.java b/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryPageView.java index 85b3be6b0..37412bbf8 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryPageView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryPageView.java @@ -33,6 +33,8 @@ import com.hippo.yorozuya.LayoutUtils; public class GalleryPageView extends GLFrameLayout { + public static final int INVALID_INDEX = -1; + private static final int INFO_INTERVAL = 24; public static final float PROGRESS_GONE = -1.0f; public static final float PROGRESS_INDETERMINATE = -2.0f; @@ -43,6 +45,8 @@ public class GalleryPageView extends GLFrameLayout { private GLTextureView mError; private GLProgressView mProgress; + private int mIndex = INVALID_INDEX; + public GalleryPageView(Context context, MovableTextTexture pageTextTexture, int progressColor, int progressSize) { // Add image @@ -87,6 +91,14 @@ public class GalleryPageView extends GLFrameLayout { mInfo.addComponent(mProgress, lp); } + int getIndex() { + return mIndex; + } + + void setIndex(int index) { + mIndex = index; + } + public void showImage() { mImage.setVisibility(VISIBLE); mInfo.setVisibility(GONE); diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryView.java b/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryView.java index 76be73398..c936ecd90 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryView.java @@ -20,7 +20,6 @@ import android.content.Context; import android.graphics.Rect; import android.support.annotation.IntDef; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.view.MotionEvent; import com.hippo.ehviewer.R; @@ -38,11 +37,13 @@ import com.hippo.gl.widget.GLEdgeView; import com.hippo.gl.widget.GLProgressView; import com.hippo.gl.widget.GLTextureView; import com.hippo.yorozuya.LayoutUtils; +import com.hippo.yorozuya.Pool; import com.hippo.yorozuya.ResourcesUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Stack; +import java.util.ArrayList; +import java.util.List; public class GalleryView extends GLView implements GestureRecognizer.Listener { @@ -60,7 +61,6 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { private @interface LayoutMode {} public static final int BACKGROUND_COLOR = 0xff212121; - private static final int INTERVAL_IN_DP = 24; private static final int PROGRESS_SIZE_IN_DP = 48; private static final int PAGE_MIN_HEIGHT_IN_DP = 256; @@ -84,37 +84,55 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { private static final float[] RIGHT_AREA = {2.0f / 3.0f, 0.0f, 1.0f, 1f}; private static final float[] CENTER_AREA = {1.0f / 3.0f, 2.0f / 5.0f, 2.0f / 3.0f, 3.0f / 5.0f}; - private Context mContext; - private MovableTextTexture mPageTextTexture; - private GestureRecognizer mGestureRecognizer; - private PageIterator mIterator; + private static final int METHOD_ON_SINGLE_TAP_UP = 0; + private static final int METHOD_ON_SINGLE_TAP_CONFIRMED = 1; + private static final int METHOD_ON_DOUBLE_TAP = 2; + private static final int METHOD_ON_DOUBLE_TAP_CONFIRMED = 3; + private static final int METHOD_ON_SCROLL = 4; + private static final int METHOD_ON_FLING = 5; + private static final int METHOD_ON_SCALE_BEGIN = 6; + private static final int METHOD_ON_SCALE = 7; + private static final int METHOD_ON_SCALE_END = 8; + private static final int METHOD_ON_DOWN = 9; + private static final int METHOD_ON_UP = 10; + private static final int METHOD_ON_POINTER_DOWN = 11; + private static final int METHOD_ON_POINTER_UP = 12; + private static final int METHOD_SET_LAYOUT_MODE = 13; + + private final Context mContext; + private final MovableTextTexture mPageTextTexture; + private final GestureRecognizer mGestureRecognizer; + private Adapter mAdapter; private PagerLayoutManager mPagerLayoutManager; private ScrollLayoutManager mScrollLayoutManager; private LayoutManager mLayoutManager; - private Recycler mRecycler; - private GLEdgeView mEdgeView; + private final Pool mGalleryPageViewPool = new Pool<>(5); + private final GLEdgeView mEdgeView; private GLProgressView mProgressCache; private GLTextureView mErrorViewCache; - private int mProgressColor; - private int mProgressSize; - private float mErrorSize; - private int mErrorColor; - private int mPageMinHeight; - private int mInterval; + private final int mProgressColor; + private final int mProgressSize; + private final float mErrorSize; + private final int mErrorColor; + private final int mPageMinHeight; private boolean mEnableRequestFill = true; + private boolean mRequestFill = false; private boolean mScale = false; private boolean mScroll = false; private boolean mFirstScroll = false; private boolean mTouched = false; - private Rect mLeftArea = new Rect(); - private Rect mRightArea = new Rect(); - private Rect mCenterArea = new Rect(); + private final Rect mLeftArea = new Rect(); + private final Rect mRightArea = new Rect(); + private final Rect mCenterArea = new Rect(); + + private final String mDefaultErrorStr; + private final String mEmptyStr; @LayoutMode private int mLayoutMode = LAYOUT_MODE_RIGHT_TO_LEFT; @@ -124,21 +142,23 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { @StartPosition private int mStartPosition = START_POSITION_TOP_LEFT; - private boolean mRequestFill = false; + private final ActionListener mListener; - private ActionListener mListener; + private final List mMethodList = new ArrayList<>(5); + private final List mArgsList = new ArrayList<>(5); + private final List mMethodListTemp = new ArrayList<>(5); + private final List mArgsListTemp = new ArrayList<>(5); - public GalleryView(Context context, PageIterator iterator, MovableTextTexture pageTextTexture, - ActionListener listener, @LayoutMode int layoutMode) { + public GalleryView(@NonNull Context context, @NonNull Adapter adapter, + MovableTextTexture pageTextTexture, ActionListener listener, @LayoutMode int layoutMode) { mContext = context; - mIterator = iterator; + mAdapter = adapter; mPageTextTexture = pageTextTexture; mListener = listener; mEdgeView = new GLEdgeView(context); mGestureRecognizer = new GestureRecognizer(context, this); - mRecycler = new Recycler(); - iterator.setGalleryView(this); + adapter.setGalleryView(this); mLayoutMode = layoutMode; mProgressColor = ResourcesUtils.getAttrColor(context, R.attr.colorPrimary); @@ -146,7 +166,9 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { mErrorSize = context.getResources().getDimension(R.dimen.text_large); mErrorColor = context.getResources().getColor(R.color.red_500); mPageMinHeight = LayoutUtils.dp2pix(context, PAGE_MIN_HEIGHT_IN_DP); - mInterval = LayoutUtils.dp2pix(context, INTERVAL_IN_DP); + + mDefaultErrorStr = context.getResources().getString(R.string.error_unknown); + mEmptyStr = context.getResources().getString(R.string.error_empty); setBackgroundColor(BACKGROUND_COLOR); } @@ -159,7 +181,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { private void ensureScrollLayoutManager() { if (mScrollLayoutManager == null) { - mScrollLayoutManager = new ScrollLayoutManager(mContext, this, mInterval); + mScrollLayoutManager = new ScrollLayoutManager(mContext, this); } } @@ -172,24 +194,26 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { case LAYOUT_MODE_LEFT_TO_RIGHT: ensurePagerLayoutManager(); mPagerLayoutManager.setMode(PagerLayoutManager.MODE_LEFT_TO_RIGHT); - mPagerLayoutManager.onAttach(mIterator); - mIterator = null; + mPagerLayoutManager.onAttach(mAdapter); + mAdapter = null; mLayoutManager = mPagerLayoutManager; break; case LAYOUT_MODE_RIGHT_TO_LEFT: ensurePagerLayoutManager(); mPagerLayoutManager.setMode(PagerLayoutManager.MODE_RIGHT_TO_LEFT); - mPagerLayoutManager.onAttach(mIterator); - mIterator = null; + mPagerLayoutManager.onAttach(mAdapter); + mAdapter = null; mLayoutManager = mPagerLayoutManager; break; case LAYOUT_MODE_TOP_TO_BOTTOM: ensureScrollLayoutManager(); - mScrollLayoutManager.onAttach(mIterator); - mIterator = null; + mScrollLayoutManager.onAttach(mAdapter); + mAdapter = null; mLayoutManager = mScrollLayoutManager; break; } + + requestFill(); } @Override @@ -197,7 +221,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { super.onDetachFromRoot(); mEdgeView.onDetachFromRoot(); - mIterator = mLayoutManager.onDetach(); + mAdapter = mLayoutManager.onDetach(); mLayoutManager = null; } @@ -225,13 +249,215 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { return mEdgeView; } - public boolean isFirstScroll() { + String getDefaultErrorStr() { + return mDefaultErrorStr; + } + + String getEmptyStr() { + return mEmptyStr; + } + + boolean isFirstScroll() { boolean firstScroll = mFirstScroll; mFirstScroll = false; return firstScroll; } + // Make sure method run in render thread to ensure thread safe + private void postMethod(int method, Object... args) { + synchronized (this) { + mMethodList.add(method); + mArgsList.add(args); + } + + invalidate(); + } + public void setLayoutMode(@LayoutMode int layoutMode) { + postMethod(METHOD_SET_LAYOUT_MODE, layoutMode); + } + + @Override + public boolean onSingleTapUp(float x, float y) { + postMethod(METHOD_ON_SINGLE_TAP_UP, x, y); + return true; + } + + @Override + public boolean onSingleTapConfirmed(float x, float y) { + postMethod(METHOD_ON_SINGLE_TAP_CONFIRMED, x, y); + return true; + } + + @Override + public boolean onDoubleTap(float x, float y) { + postMethod(METHOD_ON_DOUBLE_TAP, x, y); + return true; + } + + @Override + public boolean onDoubleTapConfirmed(float x, float y) { + postMethod(METHOD_ON_DOUBLE_TAP_CONFIRMED, x, y); + return true; + } + + @Override + public boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y) { + postMethod(METHOD_ON_SCROLL, dx, dy, totalX, totalY, x, y); + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + postMethod(METHOD_ON_FLING, velocityX, velocityY); + return true; + } + + @Override + public boolean onScaleBegin(float focusX, float focusY) { + postMethod(METHOD_ON_SCALE_BEGIN, focusX, focusY); + return true; + } + + @Override + public boolean onScale(float focusX, float focusY, float scale) { + postMethod(METHOD_ON_SCALE, focusX, focusY, scale); + return true; + } + + @Override + public void onScaleEnd() { + postMethod(METHOD_ON_SCALE_END); + } + + @Override + public void onDown(float x, float y) { + postMethod(METHOD_ON_DOWN, x, y); + } + + @Override + public void onUp() { + postMethod(METHOD_ON_UP); + } + + @Override + public void onPointerDown(float x, float y) { + postMethod(METHOD_ON_POINTER_DOWN, x, y); + } + + @Override + public void onPointerUp() { + postMethod(METHOD_ON_POINTER_UP); + } + + @Override + protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) { + mEdgeView.layout(left, top, right, bottom); + + fill(); + + if (changeSize) { + int width = right - left; + int height = bottom - top; + mLeftArea.set((int) (LEFT_AREA[0] * width), (int) (LEFT_AREA[1] * height), + (int) (LEFT_AREA[2] * width), (int) (LEFT_AREA[3] * height)); + mRightArea.set((int) (RIGHT_AREA[0] * width), (int) (RIGHT_AREA[1] * height), + (int) (RIGHT_AREA[2] * width), (int) (RIGHT_AREA[3] * height)); + mCenterArea.set((int) (CENTER_AREA[0] * width), (int) (CENTER_AREA[1] * height), + (int) (CENTER_AREA[2] * width), (int) (CENTER_AREA[3] * height)); + } + } + + @RenderThread + public void onDataChanged() { + GalleryUtils.assertInRenderThread(); + + if (mLayoutManager != null){ + mLayoutManager.onDataChanged(); + } + } + + private void onSingleTapUpInternal(float x, float y) { + } + + private void onSingleTapConfirmedInternal(float x, float y) { + } + + private void onDoubleTapInternal(float x, float y) { + } + + private void onDoubleTapConfirmedInternal(float x, float y) { + if (mScale) { + return; + } + + if (mLayoutManager != null) { + mLayoutManager.onDoubleTapConfirmed(x, y); + } + } + + private void onScrollInternal(float dx, float dy, float totalX, float totalY, float x, float y) { + if (mScale) { + return; + } + mScroll = true; + + if (mLayoutManager != null) { + mLayoutManager.onScroll(dx, dy, totalX, totalY, x, y); + } + } + + private void onFlingInternal(float velocityX, float velocityY) { + if (mLayoutManager != null) { + mLayoutManager.onFling(velocityX, velocityY); + } + } + + private void onScaleBeginInternal(float focusX, float focusY) { + onScaleInternal(focusX, focusY, 1.0f); + } + + private void onScaleInternal(float focusX, float focusY, float scale) { + if (mScroll || (mLayoutManager != null && !mLayoutManager.canScale())) { + return; + } + mScale = true; + + if (mLayoutManager != null) { + mLayoutManager.onScale(focusX, focusY, scale); + } + } + + private void onScaleEndInternal() { + } + + private void onDownInternal(float x, float y) { + mTouched = true; + mScale = false; + mScroll = false; + mFirstScroll = true; + if (mLayoutManager != null) { + mLayoutManager.onDown(); + } + } + + private void onUpInternal() { + mTouched = false; + if (mLayoutManager != null) { + mLayoutManager.onUp(); + } + } + + private void onPointerDownInternal(float x, float y) { + if (!mScroll && (mLayoutManager != null && mLayoutManager.canScale())) { + mScale = true; + } + } + + private void onPointerUpInternal() { + } + + private void setLayoutModeInternal(int layoutMode) { if (mLayoutMode == layoutMode) { return; } @@ -270,169 +496,132 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { mLayoutManager = mScrollLayoutManager; break; } + + requestFill(); } - @Override - public boolean onSingleTapUp(float x, float y) { - return true; - } - - @Override - public boolean onSingleTapConfirmed(float x, float y) { - return true; - } - - @Override - public boolean onDoubleTap(float x, float y) { - return true; - } - - @Override - public boolean onDoubleTapConfirmed(float x, float y) { - if (mScale) { - return false; - } - - if (mLayoutManager != null) { - mLayoutManager.onDoubleTapConfirmed(x, y); - } - return true; - } - - @Override - public boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y) { - if (mScale) { - return false; - } - mScroll = true; - - if (mLayoutManager != null) { - mLayoutManager.onScroll(dx, dy, totalX, totalY, x, y); - } - - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (mLayoutManager != null) { - mLayoutManager.onFling(e1, e2, velocityX, velocityY); - } - return true; - } - - @Override - public boolean onScaleBegin(float focusX, float focusY) { - return onScale(focusX, focusY, 1.0f); - } - - @Override - public boolean onScale(float focusX, float focusY, float scale) { - if (mScroll || (mLayoutManager != null && !mLayoutManager.canScale())) { - return false; - } - mScale = true; - - if (mLayoutManager != null) { - mLayoutManager.onScale(focusX, focusY, scale); - } - - return true; - } - - @Override - public void onScaleEnd() { - } - - @Override - public void onDown(float x, float y) { - mTouched = true; - mScale = false; - mScroll = false; - mFirstScroll = true; - if (mLayoutManager != null) { - mLayoutManager.onDown(); - } - } - - @Override - public void onUp() { - mTouched = false; - if (mLayoutManager != null) { - mLayoutManager.onUp(); - } - } - - @Override - public void onPointerDown(float x, float y) { - if (!mScroll && (mLayoutManager != null && mLayoutManager.canScale())) { - mScale = true; - } - } - - @Override - public void onPointerUp() { - - } - - @Override - protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) { - mEdgeView.layout(left, top, right, bottom); - + @RenderThread + void forceFill() { + mRequestFill = true; fill(); - - if (changeSize) { - int width = right - left; - int height = bottom - top; - mLeftArea.set((int) (LEFT_AREA[0] * width), (int) (LEFT_AREA[1] * height), - (int) (LEFT_AREA[2] * width), (int) (LEFT_AREA[3] * height)); - mRightArea.set((int) (RIGHT_AREA[0] * width), (int) (RIGHT_AREA[1] * height), - (int) (RIGHT_AREA[2] * width), (int) (RIGHT_AREA[3] * height)); - mCenterArea.set((int) (CENTER_AREA[0] * width), (int) (CENTER_AREA[1] * height), - (int) (CENTER_AREA[2] * width), (int) (CENTER_AREA[3] * height)); - } } @RenderThread private void fill() { - // Must be in render thread GalleryUtils.assertInRenderThread(); + if (!mRequestFill) { + return; + } + // Disable request layout mEnableRequestFill = false; if (mLayoutManager != null) { mLayoutManager.onFill(); } mEnableRequestFill = true; + mRequestFill = false; + } + + private void dispatchMethod() { + List methodListTemp = mMethodListTemp; + List argsListTemp = mArgsListTemp; + + synchronized (this) { + if (mMethodList.isEmpty()) { + return; + } + + methodListTemp.addAll(mMethodList); + argsListTemp.addAll(mArgsList); + mMethodList.clear(); + mArgsList.clear(); + } + + for (int i = 0, n = methodListTemp.size(); i < n; i++) { + int method = methodListTemp.get(i); + Object[] args = argsListTemp.get(i); + + switch (method) { + case METHOD_ON_SINGLE_TAP_UP: + onSingleTapUpInternal((Float) args[0], (Float) args[1]); + break; + case METHOD_ON_SINGLE_TAP_CONFIRMED: + onSingleTapConfirmedInternal((Float) args[0], (Float) args[1]); + break; + case METHOD_ON_DOUBLE_TAP: + onDoubleTapInternal((Float) args[0], (Float) args[1]); + break; + case METHOD_ON_DOUBLE_TAP_CONFIRMED: + onDoubleTapConfirmedInternal((Float) args[0], (Float) args[1]); + break; + case METHOD_ON_SCROLL: + onScrollInternal((Float) args[0], (Float) args[1], (Float) args[2], + (Float) args[3], (Float) args[4], (Float) args[5]); + break; + case METHOD_ON_FLING: + onFlingInternal((Float) args[0], (Float) args[1]); + break; + case METHOD_ON_SCALE_BEGIN: + onScaleBeginInternal((Float) args[0], (Float) args[1]); + break; + case METHOD_ON_SCALE: + onScaleInternal((Float) args[0], (Float) args[1], (Float) args[2]); + break; + case METHOD_ON_SCALE_END: + onScaleEndInternal(); + break; + case METHOD_ON_DOWN: + onDownInternal((Float) args[0], (Float) args[1]); + break; + case METHOD_ON_UP: + onUpInternal(); + break; + case METHOD_ON_POINTER_DOWN: + onPointerDownInternal((Float) args[0], (Float) args[1]); + break; + case METHOD_ON_POINTER_UP: + onPointerUpInternal(); + break; + case METHOD_SET_LAYOUT_MODE: + setLayoutModeInternal((Integer) args[0]); + break; + } + } + + methodListTemp.clear(); + argsListTemp.clear(); } @Override public void render(GLCanvas canvas) { + // Dispatch method + dispatchMethod(); + long time = AnimationTime.get(); if (mLayoutManager != null && mLayoutManager.onUpdateAnimation(time)) { invalidate(); } - // mSmoothScroller may do fill too, so check fill request after scroll animator - if (mRequestFill) { - mRequestFill = false; - fill(); - } + fill(); super.render(canvas); mEdgeView.render(canvas); } - public GalleryPageView findPageById(int id) { + @RenderThread + public GalleryPageView findPageByIndex(int id) { + GalleryUtils.assertInRenderThread(); + if (mLayoutManager != null) { - return mLayoutManager.findPageById(id); + return mLayoutManager.findPageByIndex(id); } else { return null; } } - public GalleryPageView obtainPage() { - GalleryPageView page = mRecycler.obtain(); + GalleryPageView obtainPage() { + GalleryPageView page = mGalleryPageViewPool.pop(); if (page == null) { page = new GalleryPageView(mContext, mPageTextTexture, mProgressColor, mProgressSize); page.setMinimumHeight(mPageMinHeight); @@ -440,14 +629,14 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { return page; } - public void releasePage(GalleryPageView page) { - mRecycler.release(page); + void releasePage(GalleryPageView page) { + mGalleryPageViewPool.push(page); } /** * Indeterminate GLProgressView */ - public GLProgressView obtainProgress() { + GLProgressView obtainProgress() { GLProgressView progress; if (mProgressCache != null) { progress = mProgressCache; @@ -466,11 +655,11 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { /** * @param progress Indeterminate GLProgressView */ - public void releaseProgress(GLProgressView progress) { + void releaseProgress(GLProgressView progress) { mProgressCache = progress; } - public GLTextureView obtainErrorView() { + GLTextureView obtainErrorView() { GLTextureView errorView; if (mErrorViewCache != null) { errorView = mErrorViewCache; @@ -481,7 +670,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { return errorView; } - public void unbindErrorView(GLTextureView errorView) { + void unbindErrorView(GLTextureView errorView) { Texture texture = errorView.getTexture(); if (texture != null) { errorView.setTexture(null); @@ -491,28 +680,19 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { } } - public void bindErrorView(GLTextureView errorView, String error) { + void bindErrorView(GLTextureView errorView, String error) { unbindErrorView(errorView); Texture texture = StringTexture.newInstance(error, mErrorSize, mErrorColor); errorView.setTexture(texture); } - public void releaseErrorView(GLTextureView errorView) { + void releaseErrorView(GLTextureView errorView) { unbindErrorView(errorView); mErrorViewCache = errorView; } - @RenderThread - public void onDataChanged() { - GalleryUtils.assertInRenderThread(); - - if (mLayoutManager != null){ - mLayoutManager.onDataChanged(); - } - } - - public static abstract class PageIterator { + public static abstract class Adapter { protected GalleryView mGalleryView; @@ -520,41 +700,26 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { mGalleryView = galleryView; } - public abstract void mark(); + public void bind(GalleryPageView view, int index) { + onBind(view, index); + view.setIndex(index); + } - public abstract void reset(); + public void unbind(GalleryPageView view) { + onUnbind(view); + view.setIndex(GalleryPageView.INVALID_INDEX); + } - /** - * @return True for waiting now, layout manager - * should show a progress bar or something like it - */ - public abstract boolean isWaiting(); + public abstract void onBind(GalleryPageView view, int index); + + public abstract void onUnbind(GalleryPageView view); /** * @return Null for no error */ public abstract String getError(); - public abstract boolean hasNext(); - - public abstract boolean hasPrevious(); - - public abstract void next(); - - public abstract void previous(); - - public void bind(GalleryPageView view) { - view.setId(onBind(view)); - } - - public void unbind(GalleryPageView view) { - onUnbind(view); - view.setId(NO_ID); - } - - public abstract int onBind(GalleryPageView view); - - public abstract void onUnbind(GalleryPageView view); + public abstract int size(); } public static abstract class LayoutManager { @@ -565,9 +730,9 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { mGalleryView = galleryView; } - public abstract void onAttach(PageIterator iterator); + public abstract void onAttach(Adapter iterator); - public abstract PageIterator onDetach(); + public abstract Adapter onDetach(); public abstract void onFill(); @@ -579,7 +744,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { public abstract void onScroll(float dx, float dy, float totalX, float totalY, float x, float y); - public abstract void onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); + public abstract void onFling(float velocityX, float velocityY); public abstract boolean canScale(); @@ -589,7 +754,12 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { public abstract void onDataChanged(); - public abstract GalleryPageView findPageById(int id); + public abstract GalleryPageView findPageByIndex(int index); + + /** + * @return {@link GalleryPageView#INVALID_INDEX} for error + */ + public abstract int getCurrentIndex(); protected void placeCenter(GLView view) { int spec = GLView.MeasureSpec.makeMeasureSpec(GLView.LayoutParams.WRAP_CONTENT, @@ -603,36 +773,6 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { } } - private static class Recycler { - - private static final int MAX_SIZE = 3; - - private int mSize = 0; - - private Stack mStack = new Stack<>(); - - @Nullable - private GalleryPageView obtain() { - if (mSize != 0) { - mSize--; - return mStack.pop(); - } else { - return null; - } - } - - public void release(@Nullable GalleryPageView page) { - if (page == null) { - return; - } - - if (mSize < MAX_SIZE) { - mSize++; - mStack.push(page); - } - } - } - public interface ActionListener { void onTapCenter(); diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gl/PagerLayoutManager.java b/app/src/main/java/com/hippo/ehviewer/gallery/gl/PagerLayoutManager.java index 78b0561fe..3a477f6be 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gl/PagerLayoutManager.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/gl/PagerLayoutManager.java @@ -19,10 +19,11 @@ package com.hippo.ehviewer.gallery.gl; import android.content.Context; import android.support.annotation.IntDef; import android.support.annotation.NonNull; -import android.view.MotionEvent; +import android.util.Log; import android.view.animation.Interpolator; import com.hippo.anani.AnimationUtils; +import com.hippo.ehviewer.gallery.GalleryProvider; import com.hippo.gl.anim.Animation; import com.hippo.gl.view.GLView; import com.hippo.gl.widget.GLEdgeView; @@ -35,9 +36,10 @@ import com.hippo.yorozuya.MathUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -// TODO Not safe ! Ensure change component in render thread public class PagerLayoutManager extends GalleryView.LayoutManager { + private static final String TAG = PagerLayoutManager.class.getSimpleName(); + @IntDef({MODE_LEFT_TO_RIGHT, MODE_RIGHT_TO_LEFT}) @Retention(RetentionPolicy.SOURCE) private @interface Mode {} @@ -55,7 +57,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { } }; - private GalleryView.PageIterator mIterator; + private GalleryView.Adapter mAdapter; private GLProgressView mProgress; private String mErrorStr; @@ -71,19 +73,22 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { private int mDeltaY; private boolean mCanScrollBetweenPages = false; - private int mInterval; + private final int mInterval; - private int[] mScrollRemain = new int[2]; - private float[] mScaleDefault = new float[3]; + private final int[] mScrollRemain = new int[2]; + private final float[] mScaleDefault = new float[3]; - private PageFling mPageFling; - private SmoothScroller mSmoothScroller; - private SmoothScaler mSmoothScaler; + private final PageFling mPageFling; + private final SmoothScroller mSmoothScroller; + private final SmoothScaler mSmoothScaler; + + // Current index + private int mIndex; public PagerLayoutManager(Context context, @NonNull GalleryView galleryView) { super(galleryView); - mInterval = LayoutUtils.dp2pix(context, INTERVAL); + mInterval = LayoutUtils.dp2pix(context, INTERVAL); mPageFling = new PageFling(context); mSmoothScroller = new SmoothScroller(); mSmoothScaler = new SmoothScaler(); @@ -108,7 +113,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { } mMode = mode; - if (mIterator != null) { + if (mAdapter != null) { // It is attached, refill // Cancel all animations cancelAllAnimations(); @@ -124,10 +129,10 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { } @Override - public void onAttach(GalleryView.PageIterator iterator) { - AssertUtils.assertEquals("The PagerLayoutManager is attached", mIterator, null); - AssertUtils.assertNotEquals("The iterator is null", iterator, null); - mIterator = iterator; + public void onAttach(GalleryView.Adapter adapter) { + AssertUtils.assertEquals("The PagerLayoutManager is attached", mAdapter, null); + AssertUtils.assertNotEquals("The adapter is null", adapter, null); + mAdapter = adapter; // Reset parameters resetParameters(); } @@ -151,7 +156,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { private void removePage(@NonNull GalleryPageView page) { mGalleryView.removeComponent(page); - mIterator.unbind(page); + mAdapter.unbind(page); mGalleryView.releasePage(page); } @@ -172,8 +177,8 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { } @Override - public GalleryView.PageIterator onDetach() { - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mIterator, null); + public GalleryView.Adapter onDetach() { + AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null); // Cancel all animations cancelAllAnimations(); @@ -184,10 +189,10 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { removeAllPages(); // Clear iterator - GalleryView.PageIterator iterator = mIterator; - mIterator = null; + GalleryView.Adapter adapter = mAdapter; + mAdapter = null; - return iterator; + return adapter; } private GalleryPageView getLeftPage() { @@ -200,18 +205,6 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { } } - private void setLeftPage(GalleryPageView page) { - switch (mMode) { - case MODE_LEFT_TO_RIGHT: - mPrevious = page; - break; - default: - case MODE_RIGHT_TO_LEFT: - mNext = page; - break; - } - } - private GalleryPageView getRightPage() { switch (mMode) { case MODE_LEFT_TO_RIGHT: @@ -222,29 +215,38 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { } } - private void setRightPage(GalleryPageView page) { - switch (mMode) { - case MODE_LEFT_TO_RIGHT: - mNext = page; - break; - default: - case MODE_RIGHT_TO_LEFT: - mPrevious = page; - break; - } - } - @Override public void onFill() { - GalleryView.PageIterator iterator = mIterator; + GalleryView.Adapter adapter = mAdapter; GalleryView galleryView = mGalleryView; - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", iterator, null); + AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", adapter, null); int width = galleryView.getWidth(); int height = galleryView.getHeight(); - String errorStr = iterator.getError(); + int size = adapter.size(); + String errorStr = adapter.getError(); + + if (size == GalleryProvider.STATE_WAIT) { // Wait here, show progress bar + // Remove error view and all pages + removeErrorView(); + removeAllPages(); + + // Ensure progress + if (mProgress == null) { + mProgress = galleryView.obtainProgress(); + galleryView.addComponent(mProgress); + } + + // Place progress center + placeCenter(mProgress); + } else if (size <= GalleryProvider.STATE_ERROR || size == 0) { // Get error or empty, show error text + // Ensure error is not null + if (0 == size) { + errorStr = galleryView.getEmptyStr(); + } else if (null == errorStr) { + errorStr = galleryView.getDefaultErrorStr(); + } - if (errorStr != null) { // Remove progress and all pages removeProgress(); removeAllPages(); @@ -263,61 +265,66 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { // Place error view center placeCenter(mErrorView); - } else if (iterator.isWaiting()) { - // Remove error view and all pages - removeErrorView(); - removeAllPages(); - - // Ensure progress - if (mProgress == null) { - mProgress = galleryView.obtainProgress(); - galleryView.addComponent(mProgress); - } - - // Place progress center - placeCenter(mProgress); } else { // Remove progress and error view removeProgress(); removeErrorView(); + // Ensure index in range + int index = mIndex; + if (index < 0) { + index = 0; + mIndex = index; + removeAllPages(); + Log.e(TAG, "index < 0, index = " + index); + } else if (index >= size) { + index = size - 1; + mIndex = index; + removeAllPages(); + Log.e(TAG, "index >= size, index = " + index + ", size = " + size); + } + // Ensure pages if (mCurrent == null) { mCurrent = galleryView.obtainPage(); - iterator.bind(mCurrent); + adapter.bind(mCurrent, index); galleryView.addComponent(mCurrent); } - if (mPrevious == null && iterator.hasPrevious()) { - iterator.mark(); - iterator.previous(); + if (mPrevious == null && index > 0) { mPrevious = galleryView.obtainPage(); - iterator.bind(mPrevious); + adapter.bind(mPrevious, index - 1); galleryView.addComponent(mPrevious); - iterator.reset(); + } else if (mPrevious != null && index == 0) { + removePage(mPrevious); } - if (mNext == null && iterator.hasNext()) { - iterator.mark(); - iterator.next(); + if (mNext == null && index < size - 1) { mNext = galleryView.obtainPage(); - iterator.bind(mNext); + adapter.bind(mNext, index + 1); galleryView.addComponent(mNext); - iterator.reset(); + } else if (mNext != null && index == size - 1) { + removePage(mNext); } + GalleryPageView leftPage = getLeftPage(); + GalleryPageView rightPage = getRightPage(); + + // Fix offset + final int min = rightPage == null ? 0 : -width - mInterval + 1; + final int max = leftPage == null ? 0 : width + mInterval - 1; + mOffset = MathUtils.clamp(mOffset, min, max); + // Measure and layout pages - int offset = mOffset; - int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY); - int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.EXACTLY); + final int offset = mOffset; + final int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY); + final int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.EXACTLY); if (mCurrent != null) { mCurrent.measure(widthSpec, heightSpec); mCurrent.layout(offset, 0, width + offset, height); } - GalleryPageView leftPage = getLeftPage(); if (leftPage != null) { leftPage.measure(widthSpec, heightSpec); leftPage.layout(-mInterval - width + offset, 0, -mInterval + offset, height); } - GalleryPageView rightPage = getRightPage(); if (rightPage != null) { rightPage.measure(widthSpec, heightSpec); rightPage.layout(width + mInterval + offset, 0, width + mInterval + width + offset, height); @@ -381,11 +388,10 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { } private void pagePrevious() { - GalleryView.PageIterator iterator = mIterator; - if (!iterator.hasPrevious()) { + if (mIndex <= 0) { return; } - iterator.previous(); + mIndex--; if (mNext != null) { removePage(mNext); @@ -394,22 +400,20 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { mCurrent = mPrevious; mPrevious = null; - if (iterator.hasPrevious()) { - iterator.mark(); - iterator.previous(); + if (mIndex > 0) { mPrevious = mGalleryView.obtainPage(); - iterator.bind(mPrevious); + mAdapter.bind(mPrevious, mIndex - 1); mGalleryView.addComponent(mPrevious); - iterator.reset(); } } private void pageNext() { - GalleryView.PageIterator iterator = mIterator; - if (!iterator.hasNext()) { + GalleryView.Adapter adapter = mAdapter; + int size = adapter.size(); + if (mIndex >= size - 1) { return; } - iterator.next(); + mIndex++; if (mPrevious != null) { removePage(mPrevious); @@ -418,13 +422,10 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { mCurrent = mNext; mNext = null; - if (iterator.hasNext()) { - iterator.mark(); - iterator.next(); + if (mIndex < size - 1) { mNext = mGalleryView.obtainPage(); - iterator.bind(mNext); + adapter.bind(mNext, mIndex + 1); mGalleryView.addComponent(mNext); - iterator.reset(); } } @@ -589,7 +590,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { } @Override - public void onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + public void onFling(float velocityX, float velocityY) { if (mCurrent == null || mOffset != 0 || !mCurrent.getImageView().isLoaded() || !mCurrent.getImageView().canFling()) { return; @@ -625,7 +626,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { @Override public void onDataChanged() { - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mIterator, null); + AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null); // Cancel all animations cancelAllAnimations(); @@ -639,19 +640,28 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { } @Override - public GalleryPageView findPageById(int id) { - if (mCurrent != null && mCurrent.getId() == id) { + public GalleryPageView findPageByIndex(int index) { + if (mCurrent != null && mCurrent.getIndex() == index) { return mCurrent; } - if (mPrevious != null && mPrevious.getId() == id) { + if (mPrevious != null && mPrevious.getIndex() == index) { return mPrevious; } - if (mNext != null && mNext.getId() == id) { + if (mNext != null && mNext.getIndex() == index) { return mNext; } return null; } + @Override + public int getCurrentIndex() { + if (mCurrent != null) { + return mCurrent.getIndex(); + } else { + return GalleryPageView.INVALID_INDEX; + } + } + class SmoothScroller extends Animation { private int mDx; @@ -701,7 +711,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { private int mDy; private int mLastX; private int mLastY; - private int[] mTemp = new int[2]; + private final int[] mTemp = new int[2]; public PageFling(Context context) { super(context); diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gl/ScrollLayoutManager.java b/app/src/main/java/com/hippo/ehviewer/gallery/gl/ScrollLayoutManager.java index c3cfc7c19..e355a96d8 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gl/ScrollLayoutManager.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/gl/ScrollLayoutManager.java @@ -20,47 +20,57 @@ import android.content.Context; import android.graphics.Rect; import android.support.annotation.NonNull; import android.util.Log; -import android.view.MotionEvent; +import com.hippo.ehviewer.gallery.GalleryProvider; import com.hippo.gl.view.GLView; import com.hippo.gl.widget.GLEdgeView; import com.hippo.gl.widget.GLProgressView; import com.hippo.gl.widget.GLTextureView; import com.hippo.yorozuya.AssertUtils; +import com.hippo.yorozuya.LayoutUtils; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; -// TODO Not safe ! Ensure change component in render thread public class ScrollLayoutManager extends GalleryView.LayoutManager { private static final String TAG = ScrollLayoutManager.class.getSimpleName(); + private static final int INTERVAL = 24; + private static final float RESERVATIONS = 0.5f; - private GalleryView.PageIterator mIterator; + private GalleryView.Adapter mAdapter; private GLProgressView mProgress; private String mErrorStr; private GLTextureView mErrorView; - private LinkedList mPages = new LinkedList<>(); + private final LinkedList mPages = new LinkedList<>(); private int mOffset; private int mDeltaX; private int mDeltaY; + private int mFirstShownLoadedPageIndex = GalleryPageView.INVALID_INDEX; - private int mInterval; - private int mProgressSpec; + private final int mInterval; - private PageFling mPageFling; + private final PageFling mPageFling; - public ScrollLayoutManager(Context context, @NonNull GalleryView galleryView, - int interval) { + // Current index + private int mIndex; + + private final BottomState mBottomState = new BottomState(); + + class BottomState { + int bottom; + boolean hasNext; + } + + public ScrollLayoutManager(Context context, @NonNull GalleryView galleryView) { super(galleryView); - mInterval = interval; - mProgressSpec = GLView.MeasureSpec.makeMeasureSpec(GLView.LayoutParams.WRAP_CONTENT, - GLView.LayoutParams.WRAP_CONTENT); + mInterval = LayoutUtils.dp2pix(context, INTERVAL); mPageFling = new PageFling(context); } @@ -68,6 +78,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { mOffset = 0; mDeltaX = 0; mDeltaY = 0; + mFirstShownLoadedPageIndex = GalleryPageView.INVALID_INDEX; } private void cancelAllAnimations() { @@ -75,10 +86,10 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { } @Override - public void onAttach(GalleryView.PageIterator iterator) { - AssertUtils.assertEquals("The ScrollLayoutManager is attached", mIterator, null); - AssertUtils.assertNotEquals("The iterator is null", iterator, null); - mIterator = iterator; + public void onAttach(GalleryView.Adapter adapter) { + AssertUtils.assertEquals("The ScrollLayoutManager is attached", mAdapter, null); + AssertUtils.assertNotEquals("The iterator is null", adapter, null); + mAdapter = adapter; // Reset parameters resetParameters(); } @@ -102,7 +113,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { private void removePage(@NonNull GalleryPageView page) { mGalleryView.removeComponent(page); - mIterator.unbind(page); + mAdapter.unbind(page); mGalleryView.releasePage(page); } @@ -114,8 +125,8 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { } @Override - public GalleryView.PageIterator onDetach() { - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mIterator, null); + public GalleryView.Adapter onDetach() { + AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null); // Cancel all animations cancelAllAnimations(); @@ -126,17 +137,18 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { removeAllPages(); // Clear iterator - GalleryView.PageIterator iterator = mIterator; - mIterator = null; + GalleryView.Adapter iterator = mAdapter; + mAdapter = null; return iterator; } private void fillPages() { - GalleryView.PageIterator iterator = mIterator; + GalleryView.Adapter adapter = mAdapter; GalleryView galleryView = mGalleryView; int width = galleryView.getWidth(); int height = galleryView.getHeight(); + int size = adapter.size(); int minY = (int) (-height * RESERVATIONS); @@ -151,10 +163,10 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { pages.remove(); // Update offset mOffset = nextTop; - if (iterator.hasNext()) { - iterator.next(); + if (mIndex < size - 1) { + mIndex++; } else { - Log.e(TAG, "iterator should has next"); + Log.e(TAG, "The page is out of show range, but it has no next page"); } } else { break; @@ -167,11 +179,11 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY); int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.UNSPECIFIED); int pageSize = 0; - while (y >= minY && iterator.hasPrevious()) { - iterator.previous(); + while (y >= minY && mIndex > 0) { + mIndex--; GalleryPageView page = galleryView.obtainPage(); - iterator.bind(page); + mAdapter.bind(page, mIndex); galleryView.addComponent(page); mPages.add(0, page); @@ -190,20 +202,27 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { } mOffset = y; + // Fix offset, ensure it is not positive + if (mOffset > 0) { + int offset = mOffset; + // Update offset + mOffset = 0; + oldOffset -= offset; + // Translate pages + pages = mPages.iterator(); + for (int i = 0; i < pageSize; i++) { + pages.next().bounds().offset(0, -offset); + } + } + // Fill from oldOffset - iterator.mark(); - // Go to oldOffset int maxY = (int) (height * (1 + RESERVATIONS)); y = oldOffset; pages = mPages.iterator(); for (int i = 0; i < pageSize; i++) { - if (iterator.hasNext()) { - iterator.next(); - } else { - Log.e(TAG, "iterator should has next"); - } pages.next(); } + int index = mIndex + pageSize; // Do fill while (true) { GalleryPageView page; @@ -211,7 +230,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { pages = null; // New page page = galleryView.obtainPage(); - iterator.bind(page); + mAdapter.bind(page, index); galleryView.addComponent(page); mPages.add(page); } else { @@ -234,13 +253,12 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { y += mInterval; // Check has next - if (!iterator.hasNext()) { + if (index >= size - 1) { break; } - iterator.next(); + index++; } - iterator.reset(); // Remove useless page while (mPages.size() > pageSize) { @@ -250,15 +268,36 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { } @Override - public synchronized void onFill() { - GalleryView.PageIterator iterator = mIterator; + public void onFill() { + GalleryView.Adapter adapter = mAdapter; GalleryView galleryView = mGalleryView; - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", iterator, null); + AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", adapter, null); int height = galleryView.getHeight(); - String errorStr = iterator.getError(); + int size = adapter.size(); + String errorStr = adapter.getError(); + + if (size == GalleryProvider.STATE_WAIT) { // Wait here, show progress bar + // Remove error view and all pages + removeErrorView(); + removeAllPages(); + + // Ensure progress + if (mProgress == null) { + mProgress = galleryView.obtainProgress(); + galleryView.addComponent(mProgress); + } + + // Place progress center + placeCenter(mProgress); + } else if (size <= GalleryProvider.STATE_ERROR || size == 0) { // Get error or empty, show error text + // Ensure error is not null + if (0 == size) { + errorStr = galleryView.getEmptyStr(); + } else if (null == errorStr) { + errorStr = galleryView.getDefaultErrorStr(); + } - if (errorStr != null) { // Remove progress and all pages removeProgress(); removeAllPages(); @@ -277,53 +316,93 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { // Place error view center placeCenter(mErrorView); - } else if (iterator.isWaiting()) { - // Remove error view and all pages - removeErrorView(); - removeAllPages(); - - // Ensure progress - if (mProgress == null) { - mProgress = galleryView.obtainProgress(); - galleryView.addComponent(mProgress); - } - - // Place progress center - placeCenter(mProgress); } else { + LinkedList pages = mPages; + // Remove progress and error view removeProgress(); removeErrorView(); - // Find first shown loaded page + // Ensure index in range + int index = mIndex; + if (index < 0) { + Log.e(TAG, "index < 0, index = " + index); + index = 0; + mIndex = index; + removeAllPages(); + } else if (index >= size) { + Log.e(TAG, "index >= size, index = " + index + ", size = " + size); + index = size - 1; + mIndex = index; + removeAllPages(); + } + + // Find first shown loaded page top GalleryPageView firstShownLoadedPage = null; int firstShownLoadedPageTop = mOffset; - for (GalleryPageView page : mPages) { - // Check first shown loaded page - if (page.isLoaded()) { - Rect bound = page.bounds(); - int pageTop = bound.top; - int pageBottom = bound.bottom; - if ((pageTop > 0 && pageTop < height) || (pageBottom > 0 && pageBottom < height)) { + if (mFirstShownLoadedPageIndex != GalleryPageView.INVALID_INDEX) { + for (GalleryPageView page : pages) { + // Check first shown loaded page + if (mFirstShownLoadedPageIndex == page.getIndex()) { firstShownLoadedPage = page; break; } + firstShownLoadedPageTop += page.getHeight() + mInterval; } - firstShownLoadedPageTop += page.getHeight() + mInterval; } fillPages(); - // TODO Avoid page not fill bottom, but has previous - // Ensure first shown loaded page top is the same - if (firstShownLoadedPage != null && firstShownLoadedPage.isAttachedToRoot()) { + if (firstShownLoadedPage != null && mFirstShownLoadedPageIndex == firstShownLoadedPage.getIndex()) { int newTop = firstShownLoadedPage.bounds().top; if (firstShownLoadedPageTop != newTop) { mOffset += firstShownLoadedPageTop - newTop; fillPages(); } } + + // Avoid page not fill bottom, but has previous + while (true) { + int bottomDist = height - pages.getLast().bounds().bottom; + int topDist = 0 - pages.getFirst().bounds().top; + if (bottomDist > 0 && topDist > 0) { + int translate = Math.min(bottomDist, topDist); + // Update offset + mOffset += translate; + // Translate pages + for (GalleryPageView page : pages) { + page.bounds().offset(0, translate); + } + // Break if no previous + if (mIndex <= 0) { + break; + } + // Refill pages + fillPages(); + // Break if last page bottom fit gallery view bottom + if (bottomDist <= topDist) { + break; + } + } else { + break; + } + } + + // Get first shown loaded image + mFirstShownLoadedPageIndex = GalleryPageView.INVALID_INDEX; + for (GalleryPageView page : mPages) { + // Check first shown loaded page + //if (page.isLoaded()) { + Rect bound = page.bounds(); + int pageTop = bound.top; + int pageBottom = bound.bottom; + if ((pageTop > 0 && pageTop < height) || (pageBottom > 0 && pageBottom < height)) { + mFirstShownLoadedPageIndex = page.getIndex(); + break; + } + //} + } } } @@ -378,13 +457,32 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { } } + private void getBottomState() { + List pages = mPages; + int bottom = mOffset; + int i = 0; + for (GalleryPageView page : pages) { + if (i != 0) { + bottom += mInterval; + } + bottom += page.getHeight(); + i++; + } + boolean hasNext = mIndex + pages.size() < mAdapter.size(); + + BottomState bottomState = mBottomState; + bottomState.bottom = bottom; + bottomState.hasNext = hasNext; + } + // True for get top or bottom private boolean scrollInternal(float dx, float dy, boolean fling, float x, float y) { if (mPages.size() == 0) { return false; } - int height = mGalleryView.getHeight(); + GalleryView galleryView = mGalleryView; + int height = galleryView.getHeight(); boolean requestFill = false; int remainY = (int) dy; boolean result = false; @@ -392,7 +490,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { while (remainY != 0) { if (remainY < 0) { // Try to show top int limit; - if (mIterator.hasPrevious()) { + if (mIndex > 0) { limit = (int) (-height * RESERVATIONS); } else { limit = 0; @@ -405,10 +503,10 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { mDeltaX = 0; mDeltaY = 0; } else { - if (mIterator.hasPrevious()) { + if (mIndex > 0) { mOffset = limit; remainY = remainY + limit - mOffset; - onFill(); + galleryView.forceFill(); requestFill = false; mDeltaX = 0; mDeltaY = 0; @@ -425,23 +523,10 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { } } } else { // Try to show bottom - int bottom = mOffset; - boolean hasNext = true; - GalleryView.PageIterator iterator = mIterator; - iterator.mark(); - for (int i = 0, size = mPages.size(); i < size; i++) { - if (i != 0) { - bottom += mInterval; - } - bottom += mPages.get(i).getHeight(); - if (iterator.hasNext()) { - iterator.next(); - } else { - hasNext = false; - break; - } - } - iterator.reset(); + getBottomState(); + BottomState bottomState = mBottomState; + int bottom = bottomState.bottom; + boolean hasNext = bottomState.hasNext; int limit; if (hasNext) { @@ -462,7 +547,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { if (hasNext) { mOffset -= bottom - limit; remainY = remainY + limit - bottom; - onFill(); + galleryView.forceFill(); requestFill = false; mDeltaX = 0; mDeltaY = 0; @@ -493,60 +578,26 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { scrollInternal(dx, dy, false, x, y); } - private boolean hasNextPage() { - boolean hasNext = true; - GalleryView.PageIterator iterator = mIterator; - iterator.mark(); - - for (int i = 0, size = mPages.size(); i < size; i++) { - if (iterator.hasNext()) { - iterator.next(); - } else { - hasNext = false; - break; - } - } - - iterator.reset(); - return hasNext; - } - @Override - public void onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + public void onFling(float velocityX, float velocityY) { if (mPages.size() <= 0) { return; } int maxY; - if (mIterator.hasPrevious()) { + if (mIndex > 0) { maxY = Integer.MAX_VALUE; } else { maxY = -mOffset; } - int bottom = mOffset; - boolean hasNext = true; - GalleryView.PageIterator iterator = mIterator; - iterator.mark(); - for (int i = 0, size = mPages.size(); i < size; i++) { - if (i != 0) { - bottom += mInterval; - } - bottom += mPages.get(i).getHeight(); - if (iterator.hasNext()) { - iterator.next(); - } else { - hasNext = false; - break; - } - } - iterator.reset(); - + getBottomState(); + BottomState bottomState = mBottomState; int minY; - if (hasNext) { + if (bottomState.hasNext) { minY = Integer.MIN_VALUE; } else { - minY = mGalleryView.getHeight() - bottom; + minY = mGalleryView.getHeight() - bottomState.bottom; } mPageFling.startFling((int) velocityX, 0, 0, @@ -571,7 +622,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { @Override public void onDataChanged() { - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mIterator, null); + AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null); // Cancel all animations cancelAllAnimations(); @@ -585,16 +636,29 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { } @Override - public GalleryPageView findPageById(int id) { - for (int i = 0, n = mPages.size(); i < n; i++) { - GalleryPageView page = mPages.get(i); - if (page.getId() == id) { + public GalleryPageView findPageByIndex(int index) { + for (GalleryPageView page : mPages) { + if (page.getIndex() == index) { return page; } } return null; } + @Override + public int getCurrentIndex() { + int height = mGalleryView.getHeight(); + for (GalleryPageView page : mPages) { + Rect bound = page.bounds(); + int pageTop = bound.top; + int pageBottom = bound.bottom; + if ((pageTop > 0 && pageTop < height) || (pageBottom > 0 && pageBottom < height)) { + return page.getIndex(); + } + } + return GalleryPageView.INVALID_INDEX; + } + class PageFling extends Fling { private int mVelocityX; @@ -661,26 +725,13 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { @Override protected void onFinish() { - boolean topEdge = !mIterator.hasPrevious() && mOffset >= 0; + int index = mIndex; - int bottom = mOffset; - boolean hasNext = true; - GalleryView.PageIterator iterator = mIterator; - iterator.mark(); - for (int i = 0, size = mPages.size(); i < size; i++) { - if (i != 0) { - bottom += mInterval; - } - bottom += mPages.get(i).getHeight(); - if (iterator.hasNext()) { - iterator.next(); - } else { - hasNext = false; - break; - } - } - iterator.reset(); - boolean bottomEdge = !hasNext && bottom <= mGalleryView.getHeight(); + boolean topEdge = index <= 0 && mOffset >= 0; + + getBottomState(); + BottomState bottomState = mBottomState; + boolean bottomEdge = !bottomState.hasNext && bottomState.bottom <= mGalleryView.getHeight(); if (topEdge && bottomEdge) { return; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java index 90f272ff6..e0be6a280 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java @@ -22,10 +22,12 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; -import android.util.SparseIntArray; import android.view.KeyEvent; +import android.view.View; +import android.widget.TextView; import com.hippo.ehviewer.R; +import com.hippo.ehviewer.Settings; import com.hippo.ehviewer.client.data.GalleryInfo; import com.hippo.ehviewer.gallery.DirGalleryProvider; import com.hippo.ehviewer.gallery.EhGalleryProvider; @@ -37,13 +39,14 @@ import com.hippo.ehviewer.gallery.gl.GalleryView; import com.hippo.gl.glrenderer.ImageTexture; import com.hippo.gl.glrenderer.MovableTextTexture; import com.hippo.gl.view.GLRootView; -import com.hippo.gl.view.GLView; import com.hippo.image.Image; -import com.hippo.yorozuya.IntIdGenerator; +import com.hippo.util.SystemUiHelper; +import com.hippo.widget.Slider; import java.io.File; -public class GalleryActivity extends AppCompatActivity implements GalleryProviderListener { +public class GalleryActivity extends AppCompatActivity + implements GalleryProviderListener, Slider.OnSetProgressListener { public static final String ACTION_DIR = "dir"; public static final String ACTION_ZIP = "zip"; @@ -52,6 +55,7 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide public static final String KEY_ACTION = "action"; public static final String KEY_FILENAME = "filename"; public static final String KEY_GALLERY_INFO = "gallery_info"; + public static final String KEY_SHOWING = "showing"; private String mAction; private String mFilename; @@ -66,8 +70,19 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide private ImageTexture.Uploader mUploader; @Nullable private GalleryProvider mGalleryProvider; + @Nullable - private SparseIntArray mIndexIdMap; + private SystemUiHelper mSystemUiHelper; + private boolean mSystemUiShowing = false; + + @Nullable + private View mSliderPanel; + @Nullable + private TextView mLeftText; + @Nullable + private TextView mRightText; + @Nullable + private Slider mSlider; private void buildProvider() { if (mGalleryProvider != null) { @@ -116,6 +131,7 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide if (mGalleryInfo != null) { outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo); } + outState.putBoolean(KEY_SHOWING, mSystemUiShowing); } @Override @@ -139,15 +155,33 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide GLRootView glRootView = (GLRootView) findViewById(R.id.gl_root_view); mGalleryProvider.setGLRoot(glRootView); mUploader = new ImageTexture.Uploader(glRootView); - mIndexIdMap = new SparseIntArray(); mPageTextTexture = MovableTextTexture.create(Typeface.DEFAULT, getResources().getDimensionPixelSize(R.dimen.gallery_page_text), getResources().getColor(R.color.secondary_text_dark), new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}); - mGalleryView = new GalleryView(this, new GalleryPageIterator(), - mPageTextTexture, null, GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT); + mGalleryView = new GalleryView(this, new GalleryAdapter(), + mPageTextTexture, null, GalleryView.LAYOUT_MODE_TOP_TO_BOTTOM); glRootView.setContentPane(mGalleryView); + + // System UI helper + mSystemUiHelper = new SystemUiHelper(this, SystemUiHelper.LEVEL_IMMERSIVE, + SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES | SystemUiHelper.FLAG_IMMERSIVE_STICKY); + if (savedInstanceState == null || !savedInstanceState.getBoolean(KEY_SHOWING, false)) { + mSystemUiHelper.hide(); + mSystemUiShowing = false; + } else { + mSystemUiHelper.show(); + mSystemUiShowing = true; + } + + /* + mSliderPanel = findViewById(R.id.slider_panel); + mLeftText = (TextView) mSliderPanel.findViewById(R.id.left); + mRightText = (TextView) mSliderPanel.findViewById(R.id.right); + mSlider = (Slider) mSliderPanel.findViewById(R.id.slider); + mSlider.setOnSetProgressListener(this); + */ } @Override @@ -167,13 +201,29 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide mGalleryProvider.stop(); mGalleryProvider = null; } - mIndexIdMap = null; + + mLeftText = null; + mRightText = null; + mSlider = null; + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + + if (hasFocus && mSystemUiHelper != null) { + if (mSystemUiShowing) { + mSystemUiHelper.show(); + } else { + mSystemUiHelper.hide(); + } + } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // Check volume - if (false) { // TODO Setting.getVolumePage() + if (Settings.getVolumePage()) { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { // TODO do something return true; @@ -220,7 +270,7 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide @Override public boolean onKeyUp(int keyCode, KeyEvent event) { // Check volume - if (false) { // TODO Setting.getVolumePage() + if (Settings.getVolumePage()) { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { return true; @@ -244,16 +294,25 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide } private GalleryPageView findPageByIndex(int index) { - if (mIndexIdMap == null || mGalleryView == null) { + if (mGalleryView != null) { + return mGalleryView.findPageByIndex(index); + } else { return null; } + } - int id = mIndexIdMap.get(index, GLView.NO_ID); - if (id == GLView.NO_ID) { - return null; + private void updateSlider(int size) { + if (mSlider == null) { + return; } - return mGalleryView.findPageById(id); + if (size >= 0) { + mSlider.setRange(1, size); + + + } else { + // TODO hide slider + } } @Override @@ -261,6 +320,16 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide if (mGalleryView != null) { mGalleryView.onDataChanged(); } + /* + SimpleHandler.getInstance().post(new Runnable() { + @Override + public void run() { + if (mGalleryProvider != null) { + updateSlider(mGalleryProvider.size()); + } + } + }); + */ } @Override @@ -309,97 +378,49 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide } } - private class GalleryPageIterator extends GalleryView.PageIterator { - - private IntIdGenerator mIdGenerator = new IntIdGenerator(); - private int mIndex = 0; - private int mBackup = mIndex; - - @Override - public void mark() { - mBackup = mIndex; + @Override + public void onSetProgress(Slider slider, int newProgress, int oldProgress, + boolean byUser, boolean confirm) { + if (confirm && mGalleryView != null) { + //mGalleryView.goToPage(newProgress); } + } - @Override - public void reset() { - mIndex = mBackup; - } - - @Override - public boolean isWaiting() { - return mGalleryProvider != null && mGalleryProvider.size() == GalleryProvider.STATE_WAIT; - } + private class GalleryAdapter extends GalleryView.Adapter { @Override public String getError() { if (mGalleryProvider == null) { return getString(R.string.error_no_provider); - } else if (mGalleryProvider.size() == GalleryProvider.STATE_ERROR) { - String error = mGalleryProvider.getError(); - if (error != null) { - return error; - } else { - return getString(R.string.error_unknown); - } - } else if (mGalleryProvider.size() == 0) { - return getString(R.string.error_empty); + } else if (mGalleryProvider.size() <= GalleryProvider.STATE_ERROR) { + return mGalleryProvider.getError(); } return null; } @Override - public boolean hasNext() { - return mGalleryProvider != null && mIndex < mGalleryProvider.size() - 1; - } - - @Override - public boolean hasPrevious() { - return mIndex > 0; - } - - @Override - public void next() { - if (mGalleryProvider != null && mIndex >= mGalleryProvider.size() - 1) { - throw new IndexOutOfBoundsException(); + public int size() { + if (mGalleryProvider == null) { + return GalleryProvider.STATE_ERROR; + } else { + return mGalleryProvider.size(); } - mIndex++; } @Override - public void previous() { - if (mIndex <= 0) { - throw new IndexOutOfBoundsException(); - } - mIndex--; - } - - @Override - public int onBind(GalleryPageView view) { + public void onBind(GalleryPageView view, int index) { if (mGalleryProvider != null && mGalleryView != null) { - mGalleryProvider.request(mIndex); + mGalleryProvider.request(index); view.showInfo(); view.setImage(null); - view.setPage(mIndex + 1); + view.setPage(index + 1); view.setProgress(GalleryPageView.PROGRESS_INDETERMINATE); view.setError(null, null); } - - int id = mIdGenerator.nextId(); - if (mIndexIdMap != null) { - mIndexIdMap.put(mIndex, id); - } - return id; } @Override public void onUnbind(GalleryPageView view) { - if (mIndexIdMap != null) { - int index = mIndexIdMap.indexOfKey(view.getId()); - if (index >= 0) { - mIndexIdMap.removeAt(index); - } - } - view.setImage(null); view.setError(null, null); } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java index 980ea8275..3ca8bc2a5 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java @@ -35,6 +35,7 @@ import android.widget.ListAdapter; import com.hippo.app.AppCompatPreferenceActivity; import com.hippo.ehviewer.R; import com.hippo.ehviewer.ui.fragment.EhFragment; +import com.hippo.ehviewer.ui.fragment.ReadFragment; import com.hippo.vector.VectorDrawable; import java.lang.reflect.Field; @@ -46,6 +47,7 @@ public final class SettingsActivity extends AppCompatPreferenceActivity { private static final String[] ENTRY_FRAGMENTS = { EhFragment.class.getName(), + ReadFragment.class.getName(), }; private class FakeLayoutInflater extends LayoutInflater { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/fragment/ReadFragment.java b/app/src/main/java/com/hippo/ehviewer/ui/fragment/ReadFragment.java new file mode 100644 index 000000000..67613c187 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/fragment/ReadFragment.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui.fragment; + +import android.os.Bundle; +import android.preference.PreferenceFragment; + +import com.hippo.ehviewer.R; + +public class ReadFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.read_settings); + } +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelper.java b/app/src/main/java/com/hippo/util/SystemUiHelper.java new file mode 100644 index 000000000..7537f5f40 --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelper.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.app.Activity; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.view.WindowManager; + +/** + * Helper for controlling the visibility of the System UI across the various API levels. To use + * this API, instantiate an instance of this class with the required level. The level specifies the + * extent to which the System UI's visibility is changed when you call {@link #hide()} + * or {@link #toggle()}. + */ +public final class SystemUiHelper { + + /** + * In this level, the helper will toggle low profile mode. + */ + public static final int LEVEL_LOW_PROFILE = 0; + + /** + * In this level, the helper will toggle the visibility of the status bar. + * If there is a navigation bar, it will toggle low profile mode. + */ + public static final int LEVEL_HIDE_STATUS_BAR = 1; + + /** + * In this level, the helper will toggle the visibility of the navigation bar + * (if present and if possible) and status bar. In cases where the navigation + * bar is present but cannot be hidden, it will toggle low profile mode. + */ + public static final int LEVEL_LEAN_BACK = 2; + + /** + * In this level, the helper will toggle the visibility of the navigation bar + * (if present and if possible) and status bar, in an immersive mode. This means that the app + * will continue to receive all touch events. The user can reveal the system bars with an + * inward swipe along the region where the system bars normally appear. + * + *

The {@link #FLAG_IMMERSIVE_STICKY} flag can be used to control how the system bars are + * displayed. + */ + public static final int LEVEL_IMMERSIVE = 3; + + /** + * When this flag is set, the + * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN} + * flag will be set on older devices, making the status bar "float" on top + * of the activity layout. This is most useful when there are no controls at + * the top of the activity layout. + *

+ * This flag isn't used on newer devices because the action + * bar, the most important structural element of an Android app, should + * be visible and not obscured by the system UI. + */ + public static final int FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES = 0x1; + + /** + * Used with {@link #LEVEL_IMMERSIVE}. When this flag is set, an inward swipe in the system + * bars areas will cause the system bars to temporarily appear in a semi-transparent state, + * but no flags are cleared, and your system UI visibility change listeners are not triggered. + * The bars automatically hide again after a short delay, or if the user interacts with the + * middle of the screen. + */ + public static final int FLAG_IMMERSIVE_STICKY = 0x2; + + private static final String LOG_TAG = SystemUiHelper.class.getSimpleName(); + + private final SystemUiHelperImpl mImpl; + + private final Handler mHandler; + private final Runnable mHideRunnable; + + /** + * Construct a new SystemUiHelper. + * + * @param activity The Activity who's system UI should be changed + * @param level The level of hiding. Should be either {@link #LEVEL_LOW_PROFILE}, + * {@link #LEVEL_HIDE_STATUS_BAR}, {@link #LEVEL_LEAN_BACK} or + * {@link #LEVEL_IMMERSIVE} + * @param flags Additional options. See {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES} and + * {@link #FLAG_IMMERSIVE_STICKY} + */ + public SystemUiHelper(Activity activity, int level, int flags) { + this(activity, level, flags, null); + } + + /** + * Construct a new SystemUiHelper. + * + * @param activity The Activity who's system UI should be changed + * @param level The level of hiding. Should be either {@link #LEVEL_LOW_PROFILE}, + * {@link #LEVEL_HIDE_STATUS_BAR}, {@link #LEVEL_LEAN_BACK} or + * {@link #LEVEL_IMMERSIVE} + * @param flags Additional options. See {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES} and + * {@link #FLAG_IMMERSIVE_STICKY} + * @param listener A listener which is called when the system visibility is changed + */ + public SystemUiHelper(Activity activity, int level, int flags, + OnVisibilityChangeListener listener) { + + mHandler = new Handler(Looper.getMainLooper()); + mHideRunnable = new HideRunnable(); + + // Create impl + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mImpl = new SystemUiHelperImplKK(activity, level, flags, listener); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mImpl = new SystemUiHelperImplJB(activity, level, flags, listener); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + mImpl = new SystemUiHelperImplICS(activity, level, flags, listener); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mImpl = new SystemUiHelperImplHC(activity, level, flags, listener); + } else { + mImpl = new SystemUiHelperImplBase(activity, level, flags, listener); + } + } + + /** + * @return true if the system UI is currently showing. What this means depends on the mode this + * {@link SystemUiHelper} was instantiated with. + */ + public boolean isShowing() { + return mImpl.isShowing(); + } + + /** + * Show the system UI. What this means depends on the mode this {@link SystemUiHelper} was + * instantiated with. + * + *

Any currently queued delayed hide requests will be removed. + */ + public void show() { + // Ensure that any currently queued hide calls are removed + removeQueuedRunnables(); + + mImpl.show(); + } + + /** + * Hide the system UI. What this means depends on the mode this {@link SystemUiHelper} was + * instantiated with. + * + *

Any currently queued delayed hide requests will be removed. + */ + public void hide() { + // Ensure that any currently queued hide calls are removed + removeQueuedRunnables(); + + mImpl.hide(); + } + + /** + * Request that the system UI is hidden after a delay. + * + *

Any currently queued delayed hide requests will be removed. + * + * @param delayMillis The delay (in milliseconds) until the Runnable + * will be executed. + */ + public void delayHide(long delayMillis) { + // Ensure that any currently queued hide calls are removed + removeQueuedRunnables(); + + mHandler.postDelayed(mHideRunnable, delayMillis); + } + + /** + * Toggle whether the system UI is displayed. + */ + public void toggle() { + if (mImpl.isShowing()) { + mImpl.hide(); + } else { + mImpl.show(); + } + } + + private void removeQueuedRunnables() { + // Ensure that any currently queued hide calls are removed + mHandler.removeCallbacks(mHideRunnable); + } + + /** + * A callback interface used to listen for system UI visibility changes. + */ + public interface OnVisibilityChangeListener { + /** + * Called when the system UI visibility has changed. + * + * @param visible True if the system UI is visible. + */ + public void onVisibilityChange(boolean visible); + } + + static abstract class SystemUiHelperImpl { + + final Activity mActivity; + final int mLevel; + final int mFlags; + final OnVisibilityChangeListener mOnVisibilityChangeListener; + + boolean mIsShowing = true; + + SystemUiHelperImpl(Activity activity, int level, int flags, + OnVisibilityChangeListener onVisibilityChangeListener) { + mActivity = activity; + mLevel = level; + mFlags = flags; + mOnVisibilityChangeListener = onVisibilityChangeListener; + } + + abstract void show(); + abstract void hide(); + + boolean isShowing() { + return mIsShowing; + } + + void setIsShowing(boolean isShowing) { + mIsShowing = isShowing; + + if (mOnVisibilityChangeListener != null) { + mOnVisibilityChangeListener.onVisibilityChange(mIsShowing); + } + } + } + + /** + * Base implementation. Used on API level 10 and below. + */ + static class SystemUiHelperImplBase extends SystemUiHelperImpl { + + SystemUiHelperImplBase(Activity activity, int level, int flags, + OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + + if ((mFlags & SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES) != 0) { + mActivity.getWindow().addFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } + } + + @Override + void show() { + if (mLevel > SystemUiHelper.LEVEL_LOW_PROFILE) { + mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + setIsShowing(true); + } + } + + @Override + void hide() { + if (mLevel > SystemUiHelper.LEVEL_LOW_PROFILE) { + mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + setIsShowing(false); + } + } + } + + private class HideRunnable implements Runnable { + @Override + public void run() { + hide(); + } + } + +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelperImplHC.java b/app/src/main/java/com/hippo/util/SystemUiHelperImplHC.java new file mode 100644 index 000000000..c7195457e --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelperImplHC.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.app.Activity; +import android.os.Build; +import android.view.View; +import android.view.WindowManager; + +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +class SystemUiHelperImplHC extends SystemUiHelper.SystemUiHelperImpl + implements View.OnSystemUiVisibilityChangeListener { + + final View mDecorView; + + SystemUiHelperImplHC(Activity activity, int level, int flags, + SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + + mDecorView = activity.getWindow().getDecorView(); + mDecorView.setOnSystemUiVisibilityChangeListener(this); + } + + + @Override + void show() { + mDecorView.setSystemUiVisibility(createShowFlags()); + } + + @Override + void hide() { + mDecorView.setSystemUiVisibility(createHideFlags()); + } + + @Override + public final void onSystemUiVisibilityChange(int visibility) { + if ((visibility & createTestFlags()) != 0) { + onSystemUiHidden(); + } else { + onSystemUiShown(); + } + } + + protected void onSystemUiShown() { + ActionBar ab = mActivity.getActionBar(); + if (ab != null) { + ab.show(); + } + + mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + + setIsShowing(true); + } + + protected void onSystemUiHidden() { + ActionBar ab = mActivity.getActionBar(); + if (ab != null) { + ab.hide(); + } + + mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + + setIsShowing(false); + } + + protected int createShowFlags() { + return View.STATUS_BAR_VISIBLE; + } + + protected int createHideFlags() { + return View.STATUS_BAR_HIDDEN; + } + + protected int createTestFlags() { + return View.STATUS_BAR_HIDDEN; + } +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelperImplICS.java b/app/src/main/java/com/hippo/util/SystemUiHelperImplICS.java new file mode 100644 index 000000000..dcf2e249b --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelperImplICS.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.os.Build; +import android.view.View; + +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) +class SystemUiHelperImplICS extends SystemUiHelperImplHC { + + SystemUiHelperImplICS(Activity activity, int level, int flags, + SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + } + + @Override + protected int createShowFlags() { + return View.SYSTEM_UI_FLAG_VISIBLE; + } + + @Override + protected int createTestFlags() { + if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) { + // Intentionally override test flags. + return View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + } + + return View.SYSTEM_UI_FLAG_LOW_PROFILE; + } + + @Override + protected int createHideFlags() { + int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE; + + if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) { + flag |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + } + + return flag; + } +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelperImplJB.java b/app/src/main/java/com/hippo/util/SystemUiHelperImplJB.java new file mode 100644 index 000000000..26fb0c1f0 --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelperImplJB.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.app.Activity; +import android.os.Build; +import android.view.View; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN) +class SystemUiHelperImplJB extends SystemUiHelperImplICS { + + SystemUiHelperImplJB(Activity activity, int level, int flags, + SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + } + + @Override + protected int createShowFlags() { + int flag = super.createShowFlags(); + + if (mLevel >= SystemUiHelper.LEVEL_HIDE_STATUS_BAR) { + flag |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + + if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) { + flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + } + } + + return flag; + } + + @Override + protected int createHideFlags() { + int flag = super.createHideFlags(); + + if (mLevel >= SystemUiHelper.LEVEL_HIDE_STATUS_BAR) { + flag |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_FULLSCREEN; + + if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) { + flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + } + } + + return flag; + } + + @Override + protected void onSystemUiShown() { + if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) { + // Manually show the action bar when in low profile mode. + ActionBar ab = mActivity.getActionBar(); + if (ab != null) { + ab.show(); + } + } + + setIsShowing(true); + } + + @Override + protected void onSystemUiHidden() { + if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) { + // Manually hide the action bar when in low profile mode. + ActionBar ab = mActivity.getActionBar(); + if (ab != null) { + ab.hide(); + } + } + + setIsShowing(false); + } +} diff --git a/app/src/main/java/com/hippo/util/SystemUiHelperImplKK.java b/app/src/main/java/com/hippo/util/SystemUiHelperImplKK.java new file mode 100644 index 000000000..8bc5be6c3 --- /dev/null +++ b/app/src/main/java/com/hippo/util/SystemUiHelperImplKK.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.os.Build; +import android.view.View; + +@TargetApi(Build.VERSION_CODES.KITKAT) +class SystemUiHelperImplKK extends SystemUiHelperImplJB { + + SystemUiHelperImplKK(Activity activity, int level, int flags, + SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) { + super(activity, level, flags, onVisibilityChangeListener); + } + + @Override + protected int createHideFlags() { + int flag = super.createHideFlags(); + + if (mLevel == SystemUiHelper.LEVEL_IMMERSIVE) { + // If the client requested immersive mode, and we're on Android 4.4 + // or later, add relevant flags. Applying HIDE_NAVIGATION without + // IMMERSIVE prevents the activity from accepting all touch events, + // so we only do this on Android 4.4 and later (where IMMERSIVE is + // present). + flag |= ((mFlags & SystemUiHelper.FLAG_IMMERSIVE_STICKY) != 0) + ? View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + : View.SYSTEM_UI_FLAG_IMMERSIVE; + } + + return flag; + } + +} diff --git a/app/src/main/java/com/hippo/widget/BatteryView.java b/app/src/main/java/com/hippo/widget/BatteryView.java new file mode 100644 index 000000000..c8a4b7fcc --- /dev/null +++ b/app/src/main/java/com/hippo/widget/BatteryView.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.widget; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.os.BatteryManager; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.hippo.drawable.BatteryDrawable; +import com.hippo.ehviewer.R; + +public class BatteryView extends TextView { + + private int mColor; + private int mWarningColor; + private int mCurrentColor; + + private int mLevel = 0; + private boolean mCharging = false; + + private BatteryDrawable mDrawable; + + private boolean mAttached = false; + private boolean mIsChargerWorking = false; + + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + + @Override + @SuppressLint("SetTextI18n") + public void onReceive(Context context, Intent intent) { + + int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) + * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); + boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1) + == BatteryManager.BATTERY_STATUS_CHARGING); + + if (mLevel != level || mCharging != charging) { + mLevel = level; + mCharging = charging; + if (mCharging && mLevel != 100) { + startCharger(); + } else { + stopCharger(); + mDrawable.setElect(mLevel); + } + + if (level <= BatteryDrawable.WARN_LIMIT && !charging) { + setTextColor(mWarningColor); + } else { + setTextColor(mColor); + } + setText(mLevel + "%"); + } + } + }; + + private final Runnable mCharger = new Runnable() { + + private int level = 0; + + @Override + public void run() { + level += 2; + if (level > 100) { + level = 0; + } + mDrawable.setElect(level, false); + getHandler().postDelayed(mCharger, 200); + } + }; + + public BatteryView(Context context) { + super(context); + init(); + } + + public BatteryView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + init(); + + TypedArray typedArray = context.obtainStyledAttributes( + attrs, R.styleable.BatteryView, defStyleAttr, 0); + mColor = typedArray.getColor(R.styleable.BatteryView_color, Color.WHITE); + mWarningColor = typedArray.getColor(R.styleable.BatteryView_warningColor, Color.RED); + typedArray.recycle(); + + mDrawable.setColor(mColor); + mDrawable.setWarningColor(mWarningColor); + } + + private void init() { + mDrawable = new BatteryDrawable(); + int height = (int) getTextSize(); + mDrawable.setBounds(0, 0, (int) (height / 0.618f), height); + setCompoundDrawables(mDrawable, null, null, null); + } + + @Override + public void setTextColor(int color) { + if (mCurrentColor == color) { + return; + } + mCurrentColor = color; + super.setTextColor(color); + } + + private void startCharger() { + if (!mIsChargerWorking) { + getHandler().post(mCharger); + mIsChargerWorking = true; + } + } + + private void stopCharger() { + if (mIsChargerWorking) { + getHandler().removeCallbacks(mCharger); + mIsChargerWorking = false; + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (!mAttached) { + mAttached = true; + + registerReceiver(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mAttached) { + unregisterReceiver(); + stopCharger(); + mAttached = false; + } + } + + private void registerReceiver() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); + } + + private void unregisterReceiver() { + getContext().unregisterReceiver(mIntentReceiver); + } +} diff --git a/app/src/main/java/com/hippo/widget/DateUtils.java b/app/src/main/java/com/hippo/widget/DateUtils.java new file mode 100644 index 000000000..967214fe6 --- /dev/null +++ b/app/src/main/java/com/hippo/widget/DateUtils.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.widget; + +final class DateUtils { + + public static final char QUOTE = '\''; + public static final char SECONDS = 's'; + + public static boolean hasSeconds(CharSequence inFormat) { + return hasDesignator(inFormat, SECONDS); + } + + public static boolean hasDesignator(CharSequence inFormat, char designator) { + if (inFormat == null) return false; + + final int length = inFormat.length(); + + int c; + int count; + + for (int i = 0; i < length; i += count) { + count = 1; + c = inFormat.charAt(i); + + if (c == QUOTE) { + count = skipQuotedText(inFormat, i, length); + } else if (c == designator) { + return true; + } + } + + return false; + } + + private static int skipQuotedText(CharSequence s, int i, int len) { + if (i + 1 < len && s.charAt(i + 1) == QUOTE) { + return 2; + } + + int count = 1; + // skip leading quote + i++; + + while (i < len) { + char c = s.charAt(i); + + if (c == QUOTE) { + count++; + // QUOTEQUOTE -> QUOTE + if (i + 1 < len && s.charAt(i + 1) == QUOTE) { + i++; + } else { + break; + } + } else { + i++; + count++; + } + } + + return count; + } +} diff --git a/app/src/main/java/com/hippo/widget/TextClock.java b/app/src/main/java/com/hippo/widget/TextClock.java new file mode 100644 index 000000000..2cf52369c --- /dev/null +++ b/app/src/main/java/com/hippo/widget/TextClock.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.widget; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.SystemClock; +import android.provider.Settings; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.view.ViewDebug.ExportedProperty; +import android.widget.TextView; + +import java.util.Calendar; +import java.util.TimeZone; + +public class TextClock extends TextView { + + public static final CharSequence DEFAULT_FORMAT_12_HOUR = "hh:mm a"; + public static final CharSequence DEFAULT_FORMAT_24_HOUR; + + static { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) + DEFAULT_FORMAT_24_HOUR = "kk:mm"; + else + DEFAULT_FORMAT_24_HOUR = "HH:mm"; + } + + private CharSequence mFormat12; + private CharSequence mFormat24; + + @ExportedProperty + private CharSequence mFormat; + @ExportedProperty + private boolean mHasSeconds; + + private boolean mAttached; + + private Calendar mTime; + private String mTimeZone; + + private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + chooseFormat(); + onTimeChanged(); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + chooseFormat(); + onTimeChanged(); + } + }; + + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { + final String timeZone = intent.getStringExtra("time-zone"); + createTime(timeZone); + } + onTimeChanged(); + } + }; + + private final Runnable mTicker = new Runnable() { + @Override + public void run() { + onTimeChanged(); + + long now = SystemClock.uptimeMillis(); + long next = now + (1000 - now % 1000); + + getHandler().postAtTime(mTicker, next); + } + }; + + public TextClock(Context context) { + this(context, null); + } + + public TextClock(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TextClock(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + mFormat12 = DEFAULT_FORMAT_12_HOUR; + mFormat24 = DEFAULT_FORMAT_24_HOUR; + mTimeZone = TimeZone.getDefault().getID(); + + init(); + } + + private void init() { + createTime(mTimeZone); + // Wait until onAttachedToWindow() to handle the ticker + chooseFormat(false); + } + + private void createTime(String timeZone) { + if (timeZone != null) { + mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); + } else { + mTime = Calendar.getInstance(); + } + } + + public CharSequence getFormat12Hour() { + return mFormat12; + } + + public void setFormat12Hour(CharSequence format) { + mFormat12 = format; + + chooseFormat(); + onTimeChanged(); + } + + public CharSequence getFormat24Hour() { + return mFormat24; + } + + public void setFormat24Hour(CharSequence format) { + mFormat24 = format; + + chooseFormat(); + onTimeChanged(); + } + + public boolean is24HourModeEnabled() { + return DateFormat.is24HourFormat(getContext()); + } + + public String getTimeZone() { + return mTimeZone; + } + + public void setTimeZone(String timeZone) { + mTimeZone = timeZone; + + createTime(timeZone); + onTimeChanged(); + } + + /** + * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} + * depending on whether the user has selected 24-hour format. + * + * Calling this method does not schedule or unschedule the time ticker. + */ + private void chooseFormat() { + chooseFormat(true); + } + + public CharSequence getFormat() { + return mFormat; + } + + /** + * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} + * depending on whether the user has selected 24-hour format. + * + * @param handleTicker true if calling this method should schedule/unschedule the + * time ticker, false otherwise + */ + private void chooseFormat(boolean handleTicker) { + final boolean format24Requested = is24HourModeEnabled(); + + if (format24Requested) { + mFormat = mFormat24; + } else { + mFormat = mFormat12; + } + + boolean hadSeconds = mHasSeconds; + mHasSeconds = DateUtils.hasSeconds(mFormat); + + if (handleTicker && mAttached && hadSeconds != mHasSeconds) { + if (hadSeconds) getHandler().removeCallbacks(mTicker); + else mTicker.run(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (!mAttached) { + mAttached = true; + + registerReceiver(); + registerObserver(); + + createTime(mTimeZone); + + if (mHasSeconds) { + mTicker.run(); + } else { + onTimeChanged(); + } + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mAttached) { + unregisterReceiver(); + unregisterObserver(); + + getHandler().removeCallbacks(mTicker); + + mAttached = false; + } + } + + private void registerReceiver() { + final IntentFilter filter = new IntentFilter(); + + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + + getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); + } + + private void registerObserver() { + final ContentResolver resolver = getContext().getContentResolver(); + resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver); + } + + private void unregisterReceiver() { + getContext().unregisterReceiver(mIntentReceiver); + } + + private void unregisterObserver() { + final ContentResolver resolver = getContext().getContentResolver(); + resolver.unregisterContentObserver(mFormatChangeObserver); + } + + private void onTimeChanged() { + mTime.setTimeInMillis(System.currentTimeMillis()); + setText(DateFormat.format(mFormat, mTime)); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_settings_eh.png b/app/src/main/res/drawable-hdpi/ic_settings_eh.png index cc2a52bd2..3c9552b84 100644 Binary files a/app/src/main/res/drawable-hdpi/ic_settings_eh.png and b/app/src/main/res/drawable-hdpi/ic_settings_eh.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_settings_read.png b/app/src/main/res/drawable-hdpi/ic_settings_read.png new file mode 100644 index 000000000..14a7b21ef Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_settings_read.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_settings_eh.png b/app/src/main/res/drawable-mdpi/ic_settings_eh.png index d25b80e2d..06e4c57b5 100644 Binary files a/app/src/main/res/drawable-mdpi/ic_settings_eh.png and b/app/src/main/res/drawable-mdpi/ic_settings_eh.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_settings_read.png b/app/src/main/res/drawable-mdpi/ic_settings_read.png new file mode 100644 index 000000000..79a20133f Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_settings_read.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_settings_eh.png b/app/src/main/res/drawable-xhdpi/ic_settings_eh.png index cb9c99302..9f97ded8b 100644 Binary files a/app/src/main/res/drawable-xhdpi/ic_settings_eh.png and b/app/src/main/res/drawable-xhdpi/ic_settings_eh.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_settings_read.png b/app/src/main/res/drawable-xhdpi/ic_settings_read.png new file mode 100644 index 000000000..07dcbb2d9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_settings_read.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_settings_eh.png b/app/src/main/res/drawable-xxhdpi/ic_settings_eh.png index 461f819d5..d567e3d8e 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_settings_eh.png and b/app/src/main/res/drawable-xxhdpi/ic_settings_eh.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_settings_read.png b/app/src/main/res/drawable-xxhdpi/ic_settings_read.png new file mode 100644 index 000000000..2072b7ad6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_settings_read.png differ diff --git a/app/src/main/res/layout/activity_gallery.xml b/app/src/main/res/layout/activity_gallery.xml index 9b10d9580..785b83ef1 100644 --- a/app/src/main/res/layout/activity_gallery.xml +++ b/app/src/main/res/layout/activity_gallery.xml @@ -15,8 +15,70 @@ ~ limitations under the License. --> - + android:layout_height="match_parent"> + + + + + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index af622fb00..d0269ec4c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -42,6 +42,19 @@ 奇怪的错误 找不到相应的应用 无法解析链接 + 非文件夹路径 + 无 provider + 解码失败 + 未找到 + 空 + 无效 zip 文件 + 读取失败 + 越界 + 写入失败 + 解析失败 + 509 + 无效链接 + 获取 pToken 错误 本应用中内容来自互联网,部分内容可能对您的生理及心理造成难以恢复的伤害。本应用作者不会对由本应用造成的后果负责。您是否愿意继续使用该应用? 用户名不可为空 @@ -158,7 +171,6 @@ 画廊预览 跳页 - 越界 EH @@ -166,6 +178,9 @@ 尝试显示日文标题。这会使获取画廊列表花费更多时间 显示默认标题。一般是英文标题 + 阅读 + 使用音量键翻页 + 被玩坏了 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index e8e811bf4..5ce22a13c 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -108,4 +108,9 @@ + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1a6c252c9..a5bb5f0de 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -99,5 +99,6 @@ #ff212121 + #ff353535 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 8758f3fe2..fdfd0ccd6 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -74,5 +74,7 @@ 192dp 56sp + 12dp + 12dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79cf57bc9..fef302870 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -216,7 +216,10 @@ Show Japanese title Try to show Japanese title. It takes more time to get gallery list Show default title. It is usually English title - + + Read + Use volume key to turn pages + Something wrong happened diff --git a/app/src/main/res/xml/read_settings.xml b/app/src/main/res/xml/read_settings.xml new file mode 100644 index 000000000..20846f4cb --- /dev/null +++ b/app/src/main/res/xml/read_settings.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/app/src/main/res/xml/settings_headers.xml b/app/src/main/res/xml/settings_headers.xml index 0e0f18b9a..7a7d951f8 100644 --- a/app/src/main/res/xml/settings_headers.xml +++ b/app/src/main/res/xml/settings_headers.xml @@ -23,4 +23,9 @@ android:title="@string/settings_eh_title" android:icon="@drawable/ic_settings_eh"/> +

+ diff --git a/beerbelly b/beerbelly index b7f79ca74..02dcb0940 160000 --- a/beerbelly +++ b/beerbelly @@ -1 +1 @@ -Subproject commit b7f79ca745bf8ab6c5ff2bca5cd77ce5b19a6d55 +Subproject commit 02dcb0940a0f5750b4a3ea72e44314699acb98bc diff --git a/yorozuya b/yorozuya index f688e1315..65a6baafd 160000 --- a/yorozuya +++ b/yorozuya @@ -1 +1 @@ -Subproject commit f688e1315f5992674aa89c6f5d1a12449c4ee104 +Subproject commit 65a6baafd467f1f0d8dcc66316a723b30ea43727