diff --git a/res/drawable-xxhdpi/seeker_bubble.9.png b/res/drawable-xxhdpi/seeker_bubble.9.png
new file mode 100644
index 000000000..a62f45073
Binary files /dev/null and b/res/drawable-xxhdpi/seeker_bubble.9.png differ
diff --git a/res/layout/gl_root_group.xml b/res/layout/gl_root_group.xml
index fbb2f5c89..6d1f73cc9 100644
--- a/res/layout/gl_root_group.xml
+++ b/res/layout/gl_root_group.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/select_mode.xml b/res/layout/select_mode.xml
index 70f66b5fa..96f0e5552 100644
--- a/res/layout/select_mode.xml
+++ b/res/layout/select_mode.xml
@@ -26,6 +26,7 @@
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:layout_weight="1"
+ android:layout_gravity="center"
android:gravity="center"
android:text="@string/content" />
@@ -35,6 +36,7 @@
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:layout_weight="4"
+ android:layout_gravity="center"
android:entries="@array/mode_list" />
@@ -48,6 +50,7 @@
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:layout_weight="1"
+ android:layout_gravity="center"
android:gravity="center"
android:text="@string/api" />
@@ -57,7 +60,31 @@
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:layout_weight="4"
+ android:layout_gravity="center"
android:entries="@array/api_mode_list" />
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 535fa484c..6a0b7e000 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -33,6 +33,12 @@
- exhentai.org
+
+ - 460x
+ - 780x
+ - 980x
+
+
- @android:string/copy
- @string/uploader_others
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 7b5518395..a53d32607 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -61,6 +61,7 @@
#fffafafa
#ff212121
#ffe5e5e5
+ #ff353535
#de000000
#8a000000
diff --git a/src/com/hippo/ehviewer/ehclient/EhInfo.java b/src/com/hippo/ehviewer/ehclient/EhInfo.java
index 422f1573c..e4d1be459 100644
--- a/src/com/hippo/ehviewer/ehclient/EhInfo.java
+++ b/src/com/hippo/ehviewer/ehclient/EhInfo.java
@@ -173,7 +173,8 @@ public class EhInfo {
public void setCookie(HttpURLConnection conn, String previewMode) {
String cookie = "ipb_member_id=" + mInfoPref.getString(KEY_MEMBER_ID, DEFAULT_MEMBER_ID) +
"; ipb_pass_hash=" + mInfoPref.getString(KEY_PASS_HASH, DEFAULT_PASS_HASH) +
- "; uconfig=" + (previewMode == null ? mUconfig : getUconfigString(previewMode));
+ "; uconfig=" + (previewMode == null ? mUconfig : getUconfigString(previewMode)) +
+ "; xres=" + Config.getLofiResolution();
conn.setRequestProperty("Cookie", cookie);
}
diff --git a/src/com/hippo/ehviewer/ehclient/ExDownloader.java b/src/com/hippo/ehviewer/ehclient/ExDownloader.java
index 7f7e3076a..237a7be98 100644
--- a/src/com/hippo/ehviewer/ehclient/ExDownloader.java
+++ b/src/com/hippo/ehviewer/ehclient/ExDownloader.java
@@ -192,9 +192,9 @@ public class ExDownloader implements Runnable {
if (mImageNum != -1) {
return mImageNum;
} else {
- return Math.max(Math.max(mStartIndex - 1,
+ return Math.max(Math.max(Math.max(mStartIndex - 1,
Math.max(mCurMaxPreviewPage, mPreviewPageNum - 1) * mPreviewPerPage),
- mPageTokeArray.maxValidIndex());
+ mPageTokeArray.maxValidIndex()), 0);
}
}
@@ -425,7 +425,6 @@ public class ExDownloader implements Runnable {
// A loop to get page token
while (true) {
-
// Check stop
if (mStopWork)
break;
@@ -448,9 +447,6 @@ public class ExDownloader implements Runnable {
}
}
- Log.d(TAG, "noTokenIndex = " + noTokenIndex);
- Log.d(TAG, "pageIndex = " + pageIndex);
-
if (noTokenIndex != -1) {
if (mPageTokeArray.get(noTokenIndex) == null) {
getDetailInfo(noTokenIndex / mPreviewPerPage, ediFile, false);
@@ -462,6 +458,8 @@ public class ExDownloader implements Runnable {
mCurRequestPageIndex = -1;
}
+ ensureWorkers();
+
synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();}
synchronized(mWorkerLock) {mWorkerLock.notifyAll();}
}
diff --git a/src/com/hippo/ehviewer/gallery/GalleryView.java b/src/com/hippo/ehviewer/gallery/GalleryView.java
index a5b6b93b9..40ec9d7a8 100644
--- a/src/com/hippo/ehviewer/gallery/GalleryView.java
+++ b/src/com/hippo/ehviewer/gallery/GalleryView.java
@@ -43,6 +43,7 @@ import com.hippo.ehviewer.util.MathUtils;
import com.hippo.ehviewer.util.TimeRunner;
import com.hippo.ehviewer.util.Ui;
import com.hippo.ehviewer.util.Utils;
+import com.hippo.ehviewer.widget.MaterialToast;
// TODO 双击加载该页
// TODO 长按从该页开始加载
@@ -126,12 +127,9 @@ public class GalleryView extends GLView
private int scrollXOffset = 0;
private int scrollYOffset = 0;
private int stopScrollXOffset = 0;
- @SuppressWarnings("unused")
private int stopScrollYOffset = 0;
private float mScale = 1;
- private boolean mShowEdegTip = true;
-
private int mWidth = -1;
private int mHeight = -1;
@@ -157,13 +155,16 @@ public class GalleryView extends GLView
private boolean mShowTapArea = false;
private boolean mShowTapAreaTurn = false;
- private OnEdgeListener mOnEdgeListener;
- private OnTapTextListener mOnTapTextListener;
- private OnScrollPageListener mOnScrollPageListener;
+ //private boolean mLocked = false;
+ //private boolean mLocked = false;
- public interface OnEdgeListener {
- public void onFirstPageEdge();
- public void onLastPageEdge();
+ private OnTapTextListener mOnTapTextListener;
+ private GalleryViewListener mGalleryViewListener;
+
+ public interface GalleryViewListener {
+ public void onTapCenter();
+ public void onPageChanged(int index);
+ public void onSizeUpdate(int size);
}
public interface OnTapTextListener {
@@ -171,20 +172,12 @@ public class GalleryView extends GLView
public void onTapDoubleText(int index);
}
- public interface OnScrollPageListener {
- public void onScrollPage(int index);
- }
-
- public void setOnEdgeListener(OnEdgeListener l) {
- mOnEdgeListener = l;
- }
-
public void setOnTapTextListener(OnTapTextListener l) {
mOnTapTextListener = l;
}
- public void setOnScrollPageListener(OnScrollPageListener l) {
- mOnScrollPageListener = l;
+ public void setGalleryViewListener(GalleryViewListener l) {
+ mGalleryViewListener = l;
}
public GalleryView(Context context, ImageSet imageSet, int startIndex) {
@@ -224,6 +217,7 @@ public class GalleryView extends GLView
@Override
public void run() {
checkSize();
+ setState();
if (!isEnsureSize && !mImageSet.isStop())
AppHandler.getInstance().postDelayed(this, 500);
}
@@ -232,6 +226,7 @@ public class GalleryView extends GLView
* Check size
*/
private void checkSize() {
+ int oldSize = mSize;
int size = mImageSet.getSize();
if (size == -1) {
isEnsureSize = false;
@@ -240,6 +235,9 @@ public class GalleryView extends GLView
isEnsureSize = true;
mSize = size;
}
+
+ if (mSize != oldSize && mGalleryViewListener != null)
+ mGalleryViewListener.onSizeUpdate(mSize);
}
/**
@@ -253,28 +251,31 @@ public class GalleryView extends GLView
mState = STATE_LAST;
}
- private boolean checkIndex(int index) {
+ private boolean isIndexLoadable(int index) {
int targetIndex = index - mCurIndex + 1;
return targetIndex == CUR_TARGET_INDEX ||
(mState != STATE_FIRST && targetIndex == PRE_TARGET_INDEX) ||
(mState != STATE_LAST && targetIndex == NEXT_TARGET_INDEX);
}
+ private boolean isIndexValid(int index) {
+ return index >= 0 && index < mSize;
+ }
+
@Override
public void onGetImage(int index) {
- if (checkIndex(index)) {
+ if (isIndexLoadable(index)) {
loadImage(index);
}
}
@Override
- public void onDownloading(int index, float percent) {
+ public synchronized void onDownloading(int index, float percent) {
int targetIndex = index - mCurIndex + 1;
- if (checkIndex(index)) {
+ if (isIndexLoadable(index)) {
// Free what do not need
- ShowItem showItem = showItems[targetIndex];
- if (showItem != null)
- showItem.recycle();
+ if (showItems[targetIndex] != null)
+ showItems[targetIndex].recycle();
showItems[targetIndex] = new Text(
String.format("正在加载 %d%%", Math.round(percent * 100)));
@@ -283,18 +284,17 @@ public class GalleryView extends GLView
}
@Override
- public void onDecodeOver(int index, Object res) {
+ public synchronized void onDecodeOver(int index, Object res) {
int targetIndex = index - mCurIndex + 1;
- if (!checkIndex(index)) {
+ if (!isIndexLoadable(index)) {
// If it do not need any more, free
if (res != null && res instanceof Bitmap)
((Bitmap)res).recycle();
} else {
// Free what do not need
- ShowItem showItem = showItems[targetIndex];
- if (showItem != null)
- showItem.recycle();
+ if (showItems[targetIndex] != null)
+ showItems[targetIndex].recycle();
if (res == null) {
showItems[targetIndex] = new Text("读取图片错误"); // TODO
@@ -313,6 +313,14 @@ public class GalleryView extends GLView
}
}
+ public int getCurIndex() {
+ return mCurIndex;
+ }
+
+ public int getSize() {
+ return mSize;
+ }
+
private void drawTapArea(GLCanvas canvas) {
// Background
canvas.fillRect(0, 0, mWidth, mHeight, TAP_AREA_MASK_COLOR);
@@ -344,7 +352,8 @@ public class GalleryView extends GLView
}
@Override
- protected void render(GLCanvas canvas) {
+ protected synchronized void render(GLCanvas canvas) {
+ // If it is not render thread, do not render
super.render(canvas);
boolean hasMovie = false;
@@ -622,8 +631,8 @@ public class GalleryView extends GLView
*
* @param index
*/
- private void loadImage(int index) {
- if (!checkIndex(index))
+ private synchronized void loadImage(int index) {
+ if (!isIndexLoadable(index))
return;
int targetIndex = index - mCurIndex + 1;
@@ -715,14 +724,13 @@ public class GalleryView extends GLView
* You'd better resetSizePosition(PRE_TARGET_INDEX) before
* @return
*/
- private boolean goToPrePage() {
+ private synchronized boolean goToPrePage() {
if (isFirstPage())
return false;
ShowItem showItem;
- showItem = showItems[NEXT_TARGET_INDEX];
- if (showItem != null)
- showItem.recycle();
+ if (showItems[NEXT_TARGET_INDEX] != null)
+ showItems[NEXT_TARGET_INDEX].recycle();
showItems[NEXT_TARGET_INDEX] = showItems[CUR_TARGET_INDEX];
showItems[CUR_TARGET_INDEX] = showItems[PRE_TARGET_INDEX];
showItems[PRE_TARGET_INDEX] = null;
@@ -741,8 +749,8 @@ public class GalleryView extends GLView
setState();
loadImage(mCurIndex-1);
- if (mOnScrollPageListener != null) {
- mOnScrollPageListener.onScrollPage(mCurIndex);
+ if (mGalleryViewListener != null) {
+ mGalleryViewListener.onPageChanged(mCurIndex);
}
invalidate();
@@ -753,14 +761,13 @@ public class GalleryView extends GLView
* You'd better resetSizePosition(NEXT_TARGET_INDEX) before
* @return
*/
- private boolean goToNextPage() {
+ private synchronized boolean goToNextPage() {
if (isLastPage())
return false;
ShowItem showItem;
- showItem = showItems[PRE_TARGET_INDEX];
- if (showItem != null)
- showItem.recycle();
+ if (showItems[PRE_TARGET_INDEX] != null)
+ showItems[PRE_TARGET_INDEX].recycle();
showItems[PRE_TARGET_INDEX] = showItems[CUR_TARGET_INDEX];
showItems[CUR_TARGET_INDEX] = showItems[NEXT_TARGET_INDEX];
showItems[NEXT_TARGET_INDEX] = null;
@@ -778,20 +785,58 @@ public class GalleryView extends GLView
setState();
loadImage(mCurIndex+1);
- if (mOnScrollPageListener != null) {
- mOnScrollPageListener.onScrollPage(mCurIndex);
+ if (mGalleryViewListener != null) {
+ mGalleryViewListener.onPageChanged(mCurIndex);
}
invalidate();
return true;
}
+ public synchronized boolean goToPage(int index) {
+ if (!isIndexValid(index))
+ return false;
+
+ if (index == mCurIndex) {
+ return true;
+ } else if (index == mCurIndex - 1) {
+ return goToPrePage();
+ } else if (index == mCurIndex + 1) {
+ return goToNextPage();
+ } else {
+ // Free
+ if (showItems[PRE_TARGET_INDEX] != null)
+ showItems[PRE_TARGET_INDEX].recycle();
+ if (showItems[CUR_TARGET_INDEX] != null)
+ showItems[CUR_TARGET_INDEX].recycle();
+ if (showItems[NEXT_TARGET_INDEX] != null)
+ showItems[NEXT_TARGET_INDEX].recycle();
+
+ mCurIndex = index;
+ setState();
+ loadImage(mCurIndex-1);
+ loadImage(mCurIndex);
+ loadImage(mCurIndex+1);
+
+ if (mGalleryViewListener != null) {
+ mGalleryViewListener.onPageChanged(mCurIndex);
+ }
+
+ invalidate();
+ return true;
+ }
+ }
+
@Override
protected boolean onTouch(MotionEvent event) {
mGestureRecognizer.onTouchEvent(event);
return true;
}
+ private void setTouchLocked(boolean locked) {
+
+ }
+
private class MyGestureListener implements GestureRecognizer.Listener {
private boolean isScale = false;
@@ -880,21 +925,28 @@ public class GalleryView extends GLView
if (Utils.isInArea(leftArea, (int)x, (int)y)) {
// TODO goto bottom first the to pre page
resetSizePosition(PRE_TARGET_INDEX);
- if (!goToPrePage() && mOnEdgeListener != null)
- mOnEdgeListener.onFirstPageEdge();
+ if (!goToPrePage()) {
+ // Get to first page
+ MaterialToast.showToast("第一页"); // TODO
+ }
} else if (Utils.isInArea(topArea, (int)x, (int)y)) {
zoom(true);
} else if (Utils.isInArea(rightArea, (int)x, (int)y)) {
// TODO goto bottom first the to pre page
resetSizePosition(NEXT_TARGET_INDEX);
- if (!goToNextPage() && mOnEdgeListener != null)
- mOnEdgeListener.onLastPageEdge();
+ if (!goToNextPage()) {
+ // Get to last page
+ MaterialToast.showToast(isEnsureSize ? "最后一页" : "等待获取后续页面"); // TODO
+ }
} else if (Utils.isInArea(bottomArea, (int)x, (int)y)) {
zoom(false);
} else if (Utils.isInArea(centerArea, (int)x, (int)y)) {
- mShowTapArea = true;
- mShowTapAreaTurn = true;
- invalidate();
+ if (mGalleryViewListener != null)
+ mGalleryViewListener.onTapCenter();
+
+ //mShowTapArea = true;
+ //mShowTapAreaTurn = true;
+ //invalidate();
} else {
// Can't catch tap
}
@@ -914,7 +966,7 @@ public class GalleryView extends GLView
return;
Utils.vibrator(mContext, 100);
- mImageSet.setTargetIndex(mCurIndex);
+ mImageSet.setStartIndex(mCurIndex);
}
@Override
@@ -960,11 +1012,6 @@ public class GalleryView extends GLView
resetSizePosition(PRE_TARGET_INDEX);
} else {
changePage = false;
- if (mShowEdegTip) { // First page
- mShowEdegTip = false;
- if (mOnEdgeListener != null)
- mOnEdgeListener.onFirstPageEdge();
- }
}
}
else { // Go to righ
@@ -974,11 +1021,6 @@ public class GalleryView extends GLView
resetSizePosition(NEXT_TARGET_INDEX);
} else {
changePage = false;
- if (mShowEdegTip) { // last page
- mShowEdegTip = false;
- if (mOnEdgeListener != null)
- mOnEdgeListener.onLastPageEdge();
- }
}
}
}
@@ -1054,6 +1096,11 @@ public class GalleryView extends GLView
mReturnTimeRunner.start();
}
break;
+ case SCROLL_NONE:
+ if (isFirstPage())
+ MaterialToast.showToast("第一页");
+ else if (isLastPage())
+ MaterialToast.showToast(isEnsureSize ? "最后一页" : "等待获取后续页面"); // TODO
}
return true;
}
@@ -1123,7 +1170,6 @@ public class GalleryView extends GLView
@Override
public void onUp() {
- mShowEdegTip = true;
// if use onSingleTapUp, use below
diff --git a/src/com/hippo/ehviewer/gallery/data/ImageSet.java b/src/com/hippo/ehviewer/gallery/data/ImageSet.java
index 965f5f59c..d16b2e565 100644
--- a/src/com/hippo/ehviewer/gallery/data/ImageSet.java
+++ b/src/com/hippo/ehviewer/gallery/data/ImageSet.java
@@ -127,6 +127,10 @@ public class ImageSet implements ExDownloader.ListenerForImageSet {
mExDownloader.setTargetIndex(index);
}
+ public void setStartIndex(int startIndex) {
+ mExDownloader.setStartIndex(startIndex);
+ }
+
public Object getImage(int index) {
// Try to get file name
String filename = mImageFilenameArray.get(index);
diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java b/src/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java
index e59ce8c5c..867fc5b6d 100644
--- a/src/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java
+++ b/src/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java
@@ -935,9 +935,7 @@ public class GLES20Canvas implements GLCanvas {
int target = texture.getTarget();
GLES20.glBindTexture(target, texture.getId());
checkError();
- // TODO Maybe multi-thread error
- if (!bitmap.isRecycled())
- GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
+ GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
}
@Override
diff --git a/src/com/hippo/ehviewer/gallery/util/GalleryUtils.java b/src/com/hippo/ehviewer/gallery/util/GalleryUtils.java
index 348ce71b2..6c0e51804 100644
--- a/src/com/hippo/ehviewer/gallery/util/GalleryUtils.java
+++ b/src/com/hippo/ehviewer/gallery/util/GalleryUtils.java
@@ -42,6 +42,10 @@ public class GalleryUtils {
sCurrentThread = Thread.currentThread();
}
+ public static boolean isRenderThread() {
+ return sCurrentThread == Thread.currentThread();
+ }
+
public static void assertNotInRenderThread() {
if (!sWarned) {
if (Thread.currentThread() == sCurrentThread) {
diff --git a/src/com/hippo/ehviewer/ui/GalleryActivity.java b/src/com/hippo/ehviewer/ui/GalleryActivity.java
index 32097314d..dfe148908 100644
--- a/src/com/hippo/ehviewer/ui/GalleryActivity.java
+++ b/src/com/hippo/ehviewer/ui/GalleryActivity.java
@@ -16,33 +16,65 @@
package com.hippo.ehviewer.ui;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
-import android.widget.RelativeLayout;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import com.hippo.ehviewer.AppHandler;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.gallery.GalleryView;
import com.hippo.ehviewer.gallery.data.ImageSet;
import com.hippo.ehviewer.gallery.ui.GLRootView;
+import com.hippo.ehviewer.util.Config;
+import com.hippo.ehviewer.util.Constants;
+import com.hippo.ehviewer.util.Ui;
import com.hippo.ehviewer.widget.AlertButton;
import com.hippo.ehviewer.widget.DialogBuilder;
-public class GalleryActivity extends AbstractActivity {
+public class GalleryActivity extends AbstractActivity
+ implements GalleryView.GalleryViewListener, SeekBar.OnSeekBarChangeListener {
@SuppressWarnings("unused")
private final static String TAG = GalleryActivity.class.getSimpleName();
+ private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
+ private static final int ACTION_BAR_HEIGHT = 48;
+ private static final int NAV_BAR_HEIGHT = 48;
+
public final static String KEY_GID = "gid";
public final static String KEY_TOKEN = "token";
public final static String KEY_TITLE = "title";
public final static String KEY_START_INDEX = "start_index";
- private RelativeLayout mainView;
+ private View mainView;
+ private View mActionBar;
+ private View mNavBar;
+ private TextView mTitleView;
+ private TextView mSeekerBubble;
+
+ /** It store index, start from 0, so max is samller than size **/
+ private SeekBar mPageSeeker;
private ImageSet mImageSet;
+ private GalleryView mGalleryView;
+
+ private int mGid;
+ private String mTitle;
+
+ private ValueAnimator mShowAnimator;
+ private ValueAnimator mHideAnimator;
+ private long mHideTaskTimeStamp;
+
@Override
public void onOrientationChanged(int paddingTop, int paddingBottom) {
// Empty
@@ -63,17 +95,83 @@ public class GalleryActivity extends AbstractActivity {
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);}
}
+ private void initAnimator() {
+ mShowAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
+ mShowAnimator.setDuration(Constants.ANIMATE_TIME);
+ mShowAnimator.setInterpolator(ACCELERATE_INTERPOLATOR);
+ mShowAnimator.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float value = (Float)animation.getAnimatedValue();
+ mActionBar.setY(- value * Ui.dp2pix(ACTION_BAR_HEIGHT));
+ mNavBar.setY(mainView.getHeight() - (1.0f - value) * Ui.dp2pix(NAV_BAR_HEIGHT));
+ mActionBar.requestLayout();
+ mNavBar.requestLayout();
+ }
+ });
+ mShowAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mActionBar.setVisibility(View.VISIBLE);
+ mNavBar.setVisibility(View.VISIBLE);
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {}
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ });
+
+ mHideAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mHideAnimator.setDuration(Constants.ANIMATE_TIME);
+ mHideAnimator.setInterpolator(ACCELERATE_INTERPOLATOR);
+ mHideAnimator.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float value = (Float)animation.getAnimatedValue();
+ mActionBar.setY(- value * Ui.dp2pix(ACTION_BAR_HEIGHT));
+ mNavBar.setY(mainView.getHeight() - (1.0f - value) * Ui.dp2pix(NAV_BAR_HEIGHT));
+ mActionBar.requestLayout();
+ mNavBar.requestLayout();
+ }
+ });
+ mHideAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mActionBar.setVisibility(View.VISIBLE);
+ mNavBar.setVisibility(View.VISIBLE);
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mActionBar.setVisibility(View.GONE);
+ mNavBar.setVisibility(View.GONE);
+ }
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ });
+ }
+
@Override
+ @TargetApi(Build.VERSION_CODES.KITKAT)
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gl_root_group);
+ mainView = findViewById(R.id.main);
+ mActionBar = findViewById(R.id.action_bar);
+ mNavBar = findViewById(R.id.nav_bar);
+ mTitleView = (TextView)findViewById(R.id.title);
+ mPageSeeker = (SeekBar)findViewById(R.id.page_seeker);
+ mSeekerBubble = (TextView)findViewById(R.id.seeker_bubble);
+
// FullScreen
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
- mainView = (RelativeLayout)findViewById(R.id.main);
mainView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -84,12 +182,12 @@ public class GalleryActivity extends AbstractActivity {
}
Intent intent = getIntent();
- int gid = intent.getIntExtra(KEY_GID, -1);
+ mGid = intent.getIntExtra(KEY_GID, -1);
String token = intent.getStringExtra(KEY_TOKEN);
- String title = intent.getStringExtra(KEY_TITLE);
+ mTitle = intent.getStringExtra(KEY_TITLE);
int startIndex = intent.getIntExtra(KEY_START_INDEX, 0);
- if (gid == -1 || token == null || title == null) {
+ if (mGid == -1 || token == null || mTitle == null) {
new DialogBuilder(this).setTitle(R.string.error)
.setMessage("数据错误!").setPositiveButton("关闭",
new View.OnClickListener() {
@@ -100,17 +198,128 @@ public class GalleryActivity extends AbstractActivity {
}
}).create().show();
} else {
- mImageSet = new ImageSet(gid, token, title, startIndex);
- GalleryView isv = new GalleryView(getApplicationContext(), mImageSet, startIndex);
+ mImageSet = new ImageSet(mGid, token, mTitle, startIndex);
+ mGalleryView = new GalleryView(getApplicationContext(), mImageSet, startIndex);
GLRootView glrv= (GLRootView)findViewById(R.id.gl_root_view);
- glrv.setContentPane(isv);
+ glrv.setContentPane(mGalleryView);
+
+ initAnimator();
+
+ // Set listener
+ mGalleryView.setGalleryViewListener(this);
+ mPageSeeker.setOnSeekBarChangeListener(this);
+
+ updateTitle();
+ mPageSeeker.setMax(mGalleryView.getSize() - 1);
+ mPageSeeker.setProgress(startIndex);
+
+ // Show first tip
+ if (Config.getGalleryFirst()) {
+ Config.setGalleryFirst(false);
+ new DialogBuilder(this).setTitle("提示")
+ .setMessage("长按屏幕,可使下载从当前页面开始") // TODO
+ .setSimpleNegativeButton().create().show();
+ }
}
}
+ private void updateTitle() {
+ StringBuilder sb = new StringBuilder(30);
+ sb.append(mGalleryView.getCurIndex() + 1).append("/")
+ .append(mGalleryView.getSize())
+ .append(" - ").append(mGid);
+ mTitleView.setText(sb.toString());
+ }
+
+ private void startHideTask() {
+ mHideTaskTimeStamp = System.currentTimeMillis();
+ AppHandler.getInstance().postDelayed(new HideTask(mHideTaskTimeStamp), 2000);
+ }
+
+ private void cancelHideTask() {
+ mHideTaskTimeStamp = 0;
+ }
+
@Override
public void onDestroy() {
super.onDestroy();
- mImageSet.free();
+ if (mImageSet != null)
+ mImageSet.free();
+ }
+
+ @Override
+ public void onTapCenter() {
+ if (mShowAnimator.isRunning() || mHideAnimator.isRunning())
+ return;
+
+ if (mActionBar.getVisibility() == View.GONE) {
+ mShowAnimator.start();
+ startHideTask();
+ } else if (mActionBar.getVisibility() == View.VISIBLE) {
+ mHideAnimator.start();
+ }
+ }
+
+ // The task to hide actionbar
+ private class HideTask implements Runnable {
+ private final long mTimeStamp;
+
+ public HideTask(long timeStamp) {
+ mTimeStamp = timeStamp;
+ }
+
+ @Override
+ public void run() {
+ if (mTimeStamp == mHideTaskTimeStamp &&!mShowAnimator.isRunning() &&
+ !mHideAnimator.isRunning() && mActionBar.getVisibility() == View.VISIBLE)
+ mHideAnimator.start();
+ }
+ }
+
+ @Override
+ public void onPageChanged(int index) {
+ updateTitle();
+ mPageSeeker.setProgress(index);
+ }
+
+ @Override
+ public void onSizeUpdate(int size) {
+ updateTitle();
+ mPageSeeker.setMax(size - 1);
+ }
+
+ private void showSeekBubble() {
+ mSeekerBubble.setVisibility(View.VISIBLE);
+ }
+
+ private void hideSeekBubble() {
+ mSeekerBubble.setVisibility(View.GONE);
+ }
+
+ public void updateSeekBubble(int progress, int max, int thumbOffset) {
+ thumbOffset = thumbOffset + Ui.dp2pix(12);
+ int x = (mainView.getWidth() - 2 * thumbOffset) * progress / max + thumbOffset;
+ mSeekerBubble.setX(x);
+ mSeekerBubble.setText(String.valueOf(progress + 1));
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromUser) {
+ updateSeekBubble(progress, seekBar.getMax(), seekBar.getThumbOffset());
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ cancelHideTask();
+ showSeekBubble();
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ startHideTask();
+ hideSeekBubble();
+ mGalleryView.goToPage(seekBar.getProgress());
}
}
diff --git a/src/com/hippo/ehviewer/ui/GalleryListActivity.java b/src/com/hippo/ehviewer/ui/GalleryListActivity.java
index 877575212..848b9b4e4 100644
--- a/src/com/hippo/ehviewer/ui/GalleryListActivity.java
+++ b/src/com/hippo/ehviewer/ui/GalleryListActivity.java
@@ -268,6 +268,8 @@ public class GalleryListActivity extends AbstractGalleryActivity
modeSpinner.setSelection(Config.getMode());
final Spinner apiModeSpinner = (Spinner)customLayout.findViewById(R.id.api_mode_list);
apiModeSpinner.setSelection(Config.getApiMode());
+ final Spinner lofiResolutionSpinner = (Spinner)customLayout.findViewById(R.id.lofi_resolution_list);
+ lofiResolutionSpinner.setSelection(Config.getLofiResolution() - 1);
return db.setSimpleNegativeButton()
.setPositiveButton(android.R.string.ok, new View.OnClickListener() {
@Override
@@ -275,6 +277,7 @@ public class GalleryListActivity extends AbstractGalleryActivity
((AlertButton)v).dialog.dismiss();
Config.setMode(modeSpinner.getSelectedItemPosition());
Config.setApiMode(apiModeSpinner.getSelectedItemPosition());
+ Config.setLofiResolution(lofiResolutionSpinner.getSelectedItemPosition() + 1);
}
}).create();
}
diff --git a/src/com/hippo/ehviewer/util/Config.java b/src/com/hippo/ehviewer/util/Config.java
index 8822d9eb8..c4b3e394a 100644
--- a/src/com/hippo/ehviewer/util/Config.java
+++ b/src/com/hippo/ehviewer/util/Config.java
@@ -354,6 +354,21 @@ public class Config {
KEY_PREVIEW_COLUMNS_LANDSCAPE, DEFAULT_PREVIEW_COLUMNS_LANDSCAPE);
}
+ /****** GalleryActivity ******/
+
+ private static final String KEY_GALLERY_FIRST = "gallery_first";
+ private static final boolean DEFAULT_GALLERY_FIRST = true;
+
+ public static boolean getGalleryFirst() {
+ return mConfigPre.getBoolean(KEY_GALLERY_FIRST, DEFAULT_GALLERY_FIRST);
+ }
+
+ public static void setGalleryFirst(boolean galleryFirst) {
+ mConfigPre.edit().putBoolean(KEY_GALLERY_FIRST, galleryFirst).apply();
+ }
+
+
+
/****** Mode an API Mode ******/
private static final String KEY_MODE = "mode";
@@ -362,6 +377,9 @@ public class Config {
private static final String KEY_API_MODE = "api_mode";
private static final int DEFAULT_API_MODE = EhClient.MODE_G;
+ private static final String KEY_LOFI_RESOLUTION = "lofi_resolution";
+ private static final int DEFAULT_LOFI_RESOLUTION = 1;
+
public static int getMode() {
return mConfigPre.getInt(KEY_MODE, DEFAULT_MODE);
}
@@ -378,6 +396,14 @@ public class Config {
mConfigPre.edit().putInt(KEY_API_MODE, apiMode).apply();
}
+ public static int getLofiResolution() {
+ return mConfigPre.getInt(KEY_LOFI_RESOLUTION, DEFAULT_LOFI_RESOLUTION);
+ }
+
+ public static void setLofiResolution(int lofiResolution) {
+ mConfigPre.edit().putInt(KEY_LOFI_RESOLUTION, lofiResolution).apply();
+ }
+
/****** Eh Config ******/
private static final String KEY_DEFAULT_CAT = "default_cat";
diff --git a/src/com/hippo/ehviewer/util/DateUtils.java b/src/com/hippo/ehviewer/util/DateUtils.java
new file mode 100644
index 000000000..a6f8b22cd
--- /dev/null
+++ b/src/com/hippo/ehviewer/util/DateUtils.java
@@ -0,0 +1,81 @@
+/*
+ * 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.ehviewer.util;
+
+import android.text.format.DateFormat;
+
+public final class DateUtils {
+
+ @SuppressWarnings("deprecation")
+ public static boolean hasSeconds(CharSequence inFormat) {
+ return hasDesignator(inFormat, DateFormat.SECONDS);
+ }
+
+
+ @SuppressWarnings("deprecation")
+ 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 == DateFormat.QUOTE) {
+ count = skipQuotedText(inFormat, i, length);
+ } else if (c == designator) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @SuppressWarnings("deprecation")
+ private static int skipQuotedText(CharSequence s, int i, int len) {
+ if (i + 1 < len && s.charAt(i + 1) == DateFormat.QUOTE) {
+ return 2;
+ }
+
+ int count = 1;
+ // skip leading quote
+ i++;
+
+ while (i < len) {
+ char c = s.charAt(i);
+
+ if (c == DateFormat.QUOTE) {
+ count++;
+ // QUOTEQUOTE -> QUOTE
+ if (i + 1 < len && s.charAt(i + 1) == DateFormat.QUOTE) {
+ i++;
+ } else {
+ break;
+ }
+ } else {
+ i++;
+ count++;
+ }
+ }
+
+ return count;
+ }
+}
diff --git a/src/com/hippo/ehviewer/widget/TextClock.java b/src/com/hippo/ehviewer/widget/TextClock.java
new file mode 100644
index 000000000..921ac99ca
--- /dev/null
+++ b/src/com/hippo/ehviewer/widget/TextClock.java
@@ -0,0 +1,264 @@
+/*
+ * 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.ehviewer.widget;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+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.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 com.hippo.ehviewer.util.DateUtils;
+
+public class TextClock extends TextView {
+
+ public static final CharSequence DEFAULT_FORMAT_12_HOUR = "hh:mm a";
+ public static final CharSequence 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