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