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