Add SceneGalleryPreviews

This commit is contained in:
seven332 committed 2016-02-04 20:17:18 +08:00
1 parent cac0aedc42
commit 6d58477f37
24 files changed
+1282 -33

No files matched your search

@@ -27,6 +27,7 @@ import com.hippo.drawable.ImageWrapper;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.data.LargePreviewSet;
import com.hippo.network.StatusCodeException;
import com.hippo.okhttp.CookieDB;
import com.hippo.scene.SceneApplication;
@@ -52,6 +53,8 @@ public class EhApplication extends SceneApplication {
private ImageWrapperHelper mImageWrapperHelper;
private Conaco<ImageWrapper> mConaco;
private LruCacheEx<Integer, GalleryDetail> mGalleryDetailCache;
private LruCacheEx<String, LargePreviewSet> mLargePreviewSetCache;
private LruCacheEx<Integer, Integer> mPreviewPagesCache;
@Override
public void onCreate() {
@@ -179,4 +182,34 @@ public class EhApplication extends SceneApplication {
}
return application.mGalleryDetailCache;
}
@NonNull
public static LruCacheEx<String, LargePreviewSet> getLargePreviewSetCache(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mLargePreviewSetCache == null) {
// Max size 50, 3 min timeout
application.mLargePreviewSetCache = new LruCacheEx<>(50, 3 * 60 * 1000, new Comparator<String>() {
@Override
public int compare(String lhs, String rhs) {
return lhs.compareTo(rhs);
}
});
}
return application.mLargePreviewSetCache;
}
@NonNull
public static LruCacheEx<Integer, Integer> getPreviewPagesCache(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mPreviewPagesCache == null) {
// Max size 50, 3 min timeout
application.mPreviewPagesCache = new LruCacheEx<>(50, 3 * 60 * 1000, new Comparator<Integer>() {
@Override
public int compare(Integer lhs, Integer rhs) {
return lhs - rhs;
}
});
}
return application.mPreviewPagesCache;
}
}
@@ -22,20 +22,18 @@ public class EhCacheKeyFactory {
return "thumb:" + gid;
}
/**
* @param index start from 0
*/
public static String getNormalPreviewKey(int gid, int index) {
return "preview:normal:" + gid + ":" + index;
}
/**
* @param index start from 0
*/
public static String getLargePreviewKey(int gid, int index) {
return "preview:large:" + gid + ":" + index;
}
public static String getLargePreviewSetKey(int gid, int index) {
return "large_preview_set:" + gid + ":" + index;
}
public static String getImageKey(int gid, int index) {
return "image:" + gid + ":" + index;
}
@@ -40,6 +40,7 @@ public class EhClient {
public static final int METHOD_SIGN_IN = 0;
public static final int METHOD_GET_GALLERY_LIST = 1;
public static final int METHOD_GET_GALLERY_DETAIL = 2;
public static final int METHOD_GET_LARGE_PREVIEW_SET = 3;
private final ThreadPoolExecutor mRequestThreadPool;
private final OkHttpClient mOkHttpClient;
@@ -138,6 +139,16 @@ public class EhClient {
}
}
private Object getLargePreviewSet(EhConfig ehConfig, Object... params) throws Exception {
Call call = EhEngine.prepareGetLargePreviewSet(mOkHttpClient, ehConfig, (String) params[0]);
if (!mStop) {
mCall = call;
return EhEngine.doGetLargePreviewSet(call);
} else {
throw new CancelledException();
}
}
@Override
protected Object doInBackground(Object... params) {
try {
@@ -148,6 +159,8 @@ public class EhClient {
return getGalleryList(mEhConfig, params);
case METHOD_GET_GALLERY_DETAIL:
return getGalleryDetail(mEhConfig, params);
case METHOD_GET_LARGE_PREVIEW_SET:
return getLargePreviewSet(mEhConfig, params);
default:
return new IllegalStateException("Can't detect method " + mMethod);
}
@@ -18,8 +18,10 @@ package com.hippo.ehviewer.client;
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.data.LargePreviewSet;
import com.hippo.ehviewer.client.exception.CancelledException;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.ehviewer.client.parser.GalleryDetailParser;
@@ -142,4 +144,27 @@ public class EhEngine {
throw e;
}
}
public static Call prepareGetLargePreviewSet(OkHttpClient okHttpClient,
EhConfig ehConfig, String url) {
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, ehConfig).build();
return okHttpClient.newCall(request);
}
public static Pair<LargePreviewSet, Integer> doGetLargePreviewSet(Call call) throws Exception {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
code = response.code();
headers = response.headers();
body = response.body().string();
return Pair.create(GalleryDetailParser.parseLargePreview(body),GalleryDetailParser.parsePreviewPages(body));
} catch (Exception e) {
throwException(call, code, headers, body, e);
throw e;
}
}
}
@@ -33,9 +33,10 @@ public class EhUrl {
public static String getGalleryDetailUrl(int gid, String token, int index) {
StringBuilder sb = new StringBuilder(HOST_EX);
sb.append("g/").append(gid).append('/').append(token);
// TODO set hc 1 or 0
sb.append("g/").append(gid).append('/').append(token).append("/?hc=1");
if (index != 0) {
sb.append("/?p=").append(index);
sb.append("&p=").append(index);
}
return sb.toString();
}
@@ -0,0 +1,58 @@
/*
* 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.client.data;
import android.os.Parcel;
import android.os.Parcelable;
public class GalleryPreview implements Parcelable {
public String imageUrl;
public String pageUrl;
public int index;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.imageUrl);
dest.writeString(this.pageUrl);
dest.writeInt(this.index);
}
public GalleryPreview() {
}
protected GalleryPreview(Parcel in) {
this.imageUrl = in.readString();
this.pageUrl = in.readString();
this.index = in.readInt();
}
public static final Parcelable.Creator<GalleryPreview> CREATOR = new Parcelable.Creator<GalleryPreview>() {
public GalleryPreview createFromParcel(Parcel source) {
return new GalleryPreview(source);
}
public GalleryPreview[] newArray(int size) {
return new GalleryPreview[size];
}
};
}
@@ -252,7 +252,7 @@ public class GalleryDetailParser {
*/
}
private static LargePreviewSet parseLargePreview(String body) {
public static LargePreviewSet parseLargePreview(String body) {
Matcher m = LARGE_PREVIEW_PATTERN.matcher(body);
LargePreviewSet largePreviewSet = new LargePreviewSet();
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
@@ -67,11 +68,22 @@ import com.hippo.widget.LoadImageView;
import com.hippo.widget.SimpleGridLayout;
import com.hippo.widget.SimpleImageView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
// TODO Update drawer checked item
public class GalleryDetailScene extends BaseScene implements View.OnClickListener {
@IntDef({STATE_NORMAL, STATE_REFRESH, STATE_REFRESH_HEADER, STATE_FAILED})
@Retention(RetentionPolicy.SOURCE)
private @interface State {}
private static final int STATE_NORMAL = 0;
private static final int STATE_REFRESH = 1;
private static final int STATE_REFRESH_HEADER = 2;
private static final int STATE_FAILED = 3;
public final static String KEY_ACTION = "action";
public static final String ACTION_GALLERY_INFO = "action_gallery_info";
public static final String ACTION_GID_TOKEN = "action_gid_token";
@@ -144,6 +156,9 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
private GalleryDetail mGalleryDetail;
private int mRequestId;
@State
private int mState;
private void handleArgs(Bundle args) {
if (args == null) {
return;
@@ -191,6 +206,18 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
}
}
private String getToken() {
if (mGalleryDetail != null) {
return mGalleryDetail.token;
} else if (mGalleryInfo != null) {
return mGalleryInfo.token;
} else if (ACTION_GID_TOKEN.equals(mAction)) {
return mToken;
} else {
return null;
}
}
private String getUploader() {
if (mGalleryDetail != null) {
return mGalleryDetail.uploader;
@@ -324,6 +351,8 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
mComments = (LinearLayout) belowHeader.findViewById(R.id.comments);
mCommentsText = (TextView) mComments.findViewById(R.id.comments_text);
RippleSalon.addRipple(mComments, false);
mComments.setOnClickListener(this);
mPreviews = belowHeader.findViewById(R.id.previews);
mGridLayout = (SimpleGridLayout) mPreviews.findViewById(R.id.grid_layout);
@@ -337,12 +366,17 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
if (mGalleryDetail != null) {
bindViewSecond();
setTransitionName();
mState = STATE_NORMAL;
} else if (mGalleryInfo != null) {
mState = STATE_REFRESH_HEADER;
bindViewFirst();
setTransitionName();
} else {
mState = STATE_REFRESH;
}
} else {
mFailedText.setText(R.string.cannot_find_gallery);
mState = STATE_FAILED;
mFailedText.setText(R.string.error_cannot_find_gallery);
}
adjustViewVisibility(false);
@@ -449,22 +483,27 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
}
private void adjustViewVisibility(boolean animation) {
if (mGalleryDetail != null) {
// Show mMainView
mViewTransition.showView(0, animation);
// Show mBelowHeader
mViewTransition2.showView(0, animation);
} else if (mGalleryInfo != null) {
// Show mMainView
mViewTransition.showView(0, animation);
// Show mProgress
mViewTransition2.showView(1, animation);
} else if (TextUtils.isEmpty(mFailedText.getText())) {
// Show mProgressView
mViewTransition.showView(1, animation);
} else {
// Show mFailedView
mViewTransition.showView(2, animation);
switch (mState) {
case STATE_NORMAL:
// Show mMainView
mViewTransition.showView(0, animation);
// Show mBelowHeader
mViewTransition2.showView(0, animation);
break;
case STATE_REFRESH:
// Show mProgressView
mViewTransition.showView(1, animation);
break;
case STATE_REFRESH_HEADER:
// Show mMainView
mViewTransition.showView(0, animation);
// Show mProgress
mViewTransition2.showView(1, animation);
break;
case STATE_FAILED:
// Show mFailedView
mViewTransition.showView(2, animation);
break;
}
}
@@ -587,9 +626,6 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
}
private void bindComments(GalleryComment[] comments) {
RippleSalon.addRipple(mComments, false);
mComments.setOnClickListener(this);
mComments.removeViews(0, mComments.getChildCount() - 1);
final int maxShowCount = 2;
@@ -629,11 +665,13 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
mPreviewText.setText(R.string.no_more_previews);
} else {
mPreviewText.setText(R.string.more_previews);
RippleSalon.addRipple(mPreviews, false);
mPreviews.setOnClickListener(this);
}
LayoutInflater inflater = getActivity().getLayoutInflater();
for (int i = 0, size = previewSet.size(); i < size; i++) {
View view = inflater.inflate(R.layout.item_preview, mGridLayout, false);
View view = inflater.inflate(R.layout.item_gallery_preview, mGridLayout, false);
mGridLayout.addView(view);
int index = previewSet.getIndexAt(i);
@@ -708,7 +746,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
PopupMenu popup = new PopupMenu(getContext(), mOtherActions, Gravity.TOP);
mPopupMenu = popup;
popup.getMenuInflater().inflate(R.menu.gallery_detail_other_action, popup.getMenu());
popup.getMenuInflater().inflate(R.menu.gallery_detail, popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
@@ -729,7 +767,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
public void onClick(View v) {
if (mFailedView == v) {
if (request()) {
mFailedText.setText("");
mState = STATE_REFRESH;
adjustViewVisibility(true);
}
} else if (mOtherActions == v) {
@@ -760,6 +798,16 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
Bundle args = new Bundle();
args.putParcelableArray(GalleryCommentsScene.KEY_COMMENTS, mGalleryDetail.comments);
startScene(GalleryCommentsScene.class, args);
} else if (mPreviews == v) {
int gid = getGid();
String token = getToken();
if (gid == -1 || token == null) {
return;
}
Bundle args = new Bundle();
args.putInt(GalleryPreviewsScene.KEY_GID, gid);
args.putString(GalleryPreviewsScene.KEY_TOKEN, token);
startScene(GalleryPreviewsScene.class, args);
} else {
Object tag;
tag = v.getTag(R.id.tag);
@@ -827,12 +875,15 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
}
private void onGetGalleryDetailSuccess(GalleryDetail result) {
mState = STATE_NORMAL;
mGalleryDetail = result;
adjustViewVisibility(true);
bindViewSecond();
}
private void onGetGalleryDetailFailure(Exception e) {
e.printStackTrace();
mState = STATE_FAILED;
String error = ExceptionUtils.getReadableString(getContext(), e);
mFailedText.setText(error);
adjustViewVisibility(true);
@@ -869,6 +920,9 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
// Put gallery detail to cache
EhApplication.getGalleryDetailCache(mApplication).put(result.gid, result);
EhApplication.getLargePreviewSetCache(mApplication).put(
EhCacheKeyFactory.getLargePreviewSetKey(result.gid, 0), result.previewSet);
EhApplication.getPreviewPagesCache(mApplication).put(result.gid, result.previewPages);
// Notify success
GalleryDetailScene scene = getScene();
@@ -0,0 +1,389 @@
/*
* 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.scene;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.data.GalleryPreview;
import com.hippo.ehviewer.client.data.LargePreviewSet;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.scene.SceneApplication;
import com.hippo.scene.SceneFragment;
import com.hippo.scene.StageActivity;
import com.hippo.vector.VectorDrawable;
import com.hippo.widget.ContentLayout;
import com.hippo.widget.LoadImageView;
import com.hippo.widget.Slider;
import com.hippo.yorozuya.LayoutUtils;
import java.util.ArrayList;
import java.util.Locale;
public class GalleryPreviewsScene extends ToolbarScene {
public static final String KEY_GID = "gid";
public static final String KEY_TOKEN = "token";
public final static String KEY_HAS_FIRST_REFRESH = "has_first_refresh";
private EhClient mClient;
private GalleryPreviewAdapter mAdapter;
private GalleryPreviewHelper mHelper;
private int mGid = -1;
private String mToken = null;
private boolean mHasFirstRefresh = false;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mClient = EhApplication.getEhClient(getContext());
if (savedInstanceState == null) {
onInit();
} else {
onRestore(savedInstanceState);
}
}
private void handlerArgs(Bundle args) {
if (args == null) {
return;
}
mGid = args.getInt(KEY_GID, -1);
mToken = args.getString(KEY_TOKEN, null);
}
private void onInit() {
handlerArgs(getArguments());
}
private void onRestore(@NonNull Bundle savedInstanceState) {
mGid = savedInstanceState.getInt(KEY_GID, -1);
mToken = savedInstanceState.getString(KEY_TOKEN, null);
mHasFirstRefresh = savedInstanceState.getBoolean(KEY_HAS_FIRST_REFRESH);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_GID, mGid);
outState.putString(KEY_TOKEN, mToken);
outState.putBoolean(KEY_HAS_FIRST_REFRESH, mHasFirstRefresh);
}
@Nullable
@Override
public View onCreateView2(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
ContentLayout contentLayout = (ContentLayout) inflater.inflate(
R.layout.scene_gallery_previews, container, false);
contentLayout.hideFastScroll();
EasyRecyclerView recyclerView = contentLayout.getRecyclerView();
mAdapter = new GalleryPreviewAdapter();
recyclerView.setAdapter(mAdapter);
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); // TODO hardcode
int padding = LayoutUtils.dp2pix(getContext(), 4);
recyclerView.setPadding(padding, padding, padding, padding);
recyclerView.setClipToPadding(false);
recyclerView.addItemDecoration(new MarginItemDecoration(padding));
mHelper = new GalleryPreviewHelper();
contentLayout.setHelper(mHelper);
// Only refresh for the first time
if (!mHasFirstRefresh) {
mHasFirstRefresh = true;
mHelper.firstRefresh();
}
return contentLayout;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setTitle(R.string.gallery_previews);
setNavigationIcon(VectorDrawable.create(getContext(), R.drawable.ic_arrow_left));
}
@Override
public int getMenuResId() {
return R.menu.gallery_previews;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.action_go_to:
int pages = mHelper.getPages();
if (pages > 0 && mHelper.canGoTo()) {
GoToDialogHelper helper = new GoToDialogHelper(pages, mHelper.getPageForTop());
AlertDialog dialog = new AlertDialog.Builder(getContext()).setTitle(R.string.go_to)
.setView(helper.getView())
.setPositiveButton(android.R.string.ok, null)
.create();
dialog.show();
helper.setPositiveButtonClickListener(dialog);
}
return true;
}
return false;
}
@Override
public void onNavigationClick() {
finish();
}
private class GalleryPreviewHolder extends RecyclerView.ViewHolder {
public LoadImageView image;
public TextView text;
public GalleryPreviewHolder(View itemView) {
super(itemView);
image = (LoadImageView) itemView.findViewById(R.id.image);
text = (TextView) itemView.findViewById(R.id.text);
}
}
private class GalleryPreviewAdapter extends RecyclerView.Adapter<GalleryPreviewHolder> {
@Override
public GalleryPreviewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new GalleryPreviewHolder(getActivity().getLayoutInflater().inflate(R.layout.item_gallery_preview, parent, false));
}
@Override
public void onBindViewHolder(GalleryPreviewHolder holder, int position) {
GalleryPreview preview = mHelper.getDataAt(position);
holder.image.load(EhCacheKeyFactory.getLargePreviewKey(mGid, preview.index), preview.imageUrl);
holder.text.setText(String.format(Locale.US, "%d", preview.index + 1));
}
@Override
public int getItemCount() {
return mHelper.size();
}
}
private class GalleryPreviewHelper extends ContentLayout.ContentHelper<GalleryPreview> {
@Override
protected void getPageData(int taskId, int type, int page) {
if (mGid == -1 || mToken == null) {
onGetExpection(taskId, new EhException(getString(R.string.error_cannot_find_gallery)));
return;
}
LargePreviewSet previewSet = EhApplication.getLargePreviewSetCache(
getContext()).get(EhCacheKeyFactory.getLargePreviewSetKey(mGid, page));
Integer pages = EhApplication.getPreviewPagesCache(getContext()).get(mGid);
if (previewSet != null && pages != null) {
onGetLargePreviewSetSuccess(Pair.create(previewSet, pages), taskId);
return;
}
String url = EhUrl.getGalleryDetailUrl(mGid, mToken, page);
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_LARGE_PREVIEW_SET);
request.setCallback(new GetLargePreviewSetListener(getContext(),
((StageActivity) getActivity()).getStageId(), getTag(), taskId, mGid, page));
request.setArgs(url);
mClient.execute(request);
}
@Override
protected Context getContext() {
return GalleryPreviewsScene.this.getContext();
}
@Override
protected void notifyDataSetChanged() {
mAdapter.notifyDataSetChanged();
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
@Override
protected void notifyItemRangeInserted(int positionStart, int itemCount) {
mAdapter.notifyItemRangeInserted(positionStart, itemCount);
}
}
private void onGetLargePreviewSetSuccess(Pair<LargePreviewSet, Integer> result, int taskId) {
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
LargePreviewSet previewSet = result.first;
int size = previewSet.size();
ArrayList<GalleryPreview> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
GalleryPreview preview = new GalleryPreview();
preview.imageUrl = previewSet.getImageUrlAt(i);
preview.pageUrl = previewSet.getPageUrlAt(i);
preview.index = previewSet.getIndexAt(i);
list.add(preview);
}
mHelper.setPages(taskId, result.second);
mHelper.onGetPageData(taskId, list);
}
}
private void onGetLargePreviewSetFailure(Exception e, int taskId) {
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
mHelper.onGetExpection(taskId, e);
}
}
private static class GetLargePreviewSetListener implements EhClient.Callback<Pair<LargePreviewSet, Integer>> {
private SceneApplication mApplication;
private int mStageId;
private String mSceneTag;
private int mTaskId;
private int mGid;
private int mPage;
public GetLargePreviewSetListener(Context context, int stageId, String sceneTag, int taskId, int gid, int page) {
mApplication = (SceneApplication) context.getApplicationContext();
mStageId = stageId;
mSceneTag = sceneTag;
mTaskId = taskId;
mGid = gid;
mPage = page;
}
private GalleryPreviewsScene getScene() {
StageActivity stage = mApplication.findStageActivityById(mStageId);
if (stage == null) {
return null;
}
SceneFragment scene = stage.findSceneByTag(mSceneTag);
if (scene instanceof GalleryPreviewsScene) {
return (GalleryPreviewsScene) scene;
} else {
return null;
}
}
@Override
public void onSuccess(Pair<LargePreviewSet, Integer> result) {
EhApplication.getLargePreviewSetCache(mApplication).put(
EhCacheKeyFactory.getLargePreviewSetKey(mGid, mPage), result.first);
EhApplication.getPreviewPagesCache(mApplication).put(mGid, result.second);
GalleryPreviewsScene scene = getScene();
if (scene != null) {
scene.onGetLargePreviewSetSuccess(result, mTaskId);
}
}
@Override
public void onFailure(Exception e) {
GalleryPreviewsScene scene = getScene();
if (scene != null) {
scene.onGetLargePreviewSetFailure(e, mTaskId);
}
}
@Override
public void onCancel() {
}
}
private class GoToDialogHelper implements View.OnClickListener,
DialogInterface.OnDismissListener {
private int mPages;
private View mView;
private Slider mSlider;
private Dialog mDialog;
@SuppressLint("InflateParams")
private GoToDialogHelper(int pages, int currentPage) {
mPages = pages;
mView = getActivity().getLayoutInflater().inflate(R.layout.dialog_go_to, null);
((TextView) mView.findViewById(R.id.start)).setText("1");
((TextView) mView.findViewById(R.id.end)).setText(String.format(Locale.US, "%d", pages));
mSlider = (Slider) mView.findViewById(R.id.slider);
mSlider.setRange(1, pages);
mSlider.setProgress(currentPage + 1);
}
public View getView() {
return mView;
}
public void setPositiveButtonClickListener(AlertDialog dialog) {
mDialog = dialog;
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
dialog.setOnDismissListener(this);
}
@Override
public void onClick(View v) {
int page = mSlider.getProgress() - 1;
if (page >= 0 && page < mPages) {
mHelper.goTo(page);
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
} else {
Toast.makeText(getContext(), R.string.error_out_of_range, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onDismiss(DialogInterface dialog) {
mDialog = null;
}
}
}
@@ -0,0 +1,522 @@
/*
* Copyright 2015 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.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsoluteLayout;
import android.widget.PopupWindow;
import com.hippo.anani.AnimationUtils;
import com.hippo.ehviewer.R;
import com.hippo.vector.VectorDrawable;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.SimpleHandler;
public class Slider extends View {
private static char[] CHARACTERS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
};
private static final int BUBBLE_WIDTH = 26;
private static final int BUBBLE_HEIGHT = 32;
private Context mContext;
private Paint mPaint;
private Paint mBgPaint;
private RectF mLeftRectF = new RectF();
private RectF mRightRectF = new RectF();
private PopupWindow mPopup;
private BubbleView mBubble;
private int mStart;
private int mEnd;
private int mProgress;
private float mPercent;
private int mDrawProgress;
private float mDrawPercent;
private int mTargetProgress;
private float mThickness;
private float mRadius;
private float mCharWidth;
private float mCharHeight;
private int mBubbleWidth;
private int mBubbleHeight;
private int mBubbleMinWidth;
private int mBubbleMinHeight;
private int mPopupX;
private int mPopupY;
private int mPopupWidth;
private int[] mTemp = new int[2];
private boolean mReverse = false;
private boolean mShowBubble;
private float mDrawBubbleScale = 0f;
private ValueAnimator mProgressAnimation;
private ValueAnimator mBubbleScaleAnimation;
private OnSetProgressListener mListener;
private CheckForShowBubble mCheckForShowBubble;
public Slider(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public Slider(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@SuppressWarnings("deprecation")
private void init(Context context, AttributeSet attrs) {
mContext = context;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextAlign(Paint.Align.CENTER);
mBubbleMinWidth = LayoutUtils.dp2pix(context, BUBBLE_WIDTH);
mBubbleMinHeight = LayoutUtils.dp2pix(context, BUBBLE_HEIGHT);
mBubble = new BubbleView(context, textPaint);
mBubble.setScaleX(0.0f);
mBubble.setScaleY(0.0f);
AbsoluteLayout absoluteLayout = new AbsoluteLayout(context);
absoluteLayout.addView(mBubble);
absoluteLayout.setBackgroundDrawable(null);
mPopup = new PopupWindow(absoluteLayout);
mPopup.setOutsideTouchable(false);
mPopup.setTouchable(false);
mPopup.setFocusable(false);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Slider);
textPaint.setColor(a.getColor(R.styleable.Slider_textColor, Color.WHITE));
textPaint.setTextSize(a.getDimensionPixelSize(R.styleable.Slider_textSize, 12));
updateTextSize();
setRange(a.getInteger(R.styleable.Slider_start, 0), a.getInteger(R.styleable.Slider_end, 0));
setProgress(a.getInteger(R.styleable.Slider_slider_progress, 0));
mThickness = a.getDimension(R.styleable.Slider_thickness, 2);
mRadius = a.getDimension(R.styleable.Slider_radius, 6);
setColor(a.getColor(R.styleable.Slider_color, Color.BLACK));
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBgPaint.setColor(a.getBoolean(R.styleable.Slider_dark, false) ? 0x4dffffff : 0x42000000);
a.recycle();
mProgressAnimation = new ValueAnimator();
mProgressAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
mProgressAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
mDrawPercent = value;
mDrawProgress = Math.round(MathUtils.lerp((float) mStart, mEnd, value));
updateBubblePosition();
mBubble.setProgress(mDrawProgress);
invalidate();
}
});
mBubbleScaleAnimation = new ValueAnimator();
mBubbleScaleAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
mDrawBubbleScale = value;
mBubble.setScaleX(value);
mBubble.setScaleY(value);
invalidate();
}
});
}
private void updateTextSize() {
int length = CHARACTERS.length;
float[] widths = new float[length];
mPaint.getTextWidths(CHARACTERS, 0, length, widths);
mCharWidth = 0.0f;
for (float f : widths) {
mCharWidth = Math.max(mCharWidth, f);
}
Paint.FontMetrics fm = mPaint.getFontMetrics();
mCharHeight = fm.bottom - fm.top;
}
private void updateBubbleSize() {
int oldWidth = mBubbleWidth;
int oldHeight = mBubbleHeight;
mBubbleWidth = (int) Math.max(mBubbleMinWidth,
Integer.toString(mEnd).length() * mCharWidth + LayoutUtils.dp2pix(mContext, 8));
mBubbleHeight = (int) Math.max(mBubbleMinHeight,
mCharHeight + LayoutUtils.dp2pix(mContext, 8));
if (oldWidth != mBubbleWidth && oldHeight != mBubbleHeight) {
//noinspection deprecation
AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) mBubble.getLayoutParams();
lp.width = mBubbleWidth;
lp.height = mBubbleHeight;
mBubble.setLayoutParams(lp);
}
}
private void updatePopup() {
int width = getWidth();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
getLocationInWindow(mTemp);
mPopupWidth = (int) (width - mRadius - mRadius + mBubbleWidth);
int popupHeight = mBubbleHeight;
mPopupX = (int) (mTemp[0] + mRadius - (mBubbleWidth / 2));
mPopupY = (int) (mTemp[1] - popupHeight + paddingTop +
((getHeight() - paddingTop - paddingBottom) / 2) -
mRadius -LayoutUtils.dp2pix(mContext, 2));
mPopup.update(mPopupX, mPopupY, mPopupWidth, popupHeight, false);
}
private void updateBubblePosition() {
float x = ((mPopupWidth - mBubbleWidth) * mDrawPercent);
mBubble.setX(x);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updatePopup();
updateBubblePosition();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mPopup.showAtLocation(this, Gravity.TOP | Gravity.LEFT, mPopupX, mPopupY);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mPopup.dismiss();
}
private void startProgressAnimation(float percent) {
float currentValue;
if (mProgressAnimation.isRunning()) {
mProgressAnimation.setCurrentPlayTime(mProgressAnimation.getCurrentPlayTime());
Object value = mProgressAnimation.getAnimatedValue();
if (value instanceof Float) {
currentValue = (float) value;
} else {
currentValue = mDrawPercent;
}
} else {
currentValue = mDrawPercent;
}
mProgressAnimation.cancel();
mProgressAnimation.setFloatValues(currentValue, percent);
mProgressAnimation.setDuration(Math.min(500, (long) (50 * getWidth() * Math.abs(currentValue - percent))));
mProgressAnimation.start();
}
private void startShowBubbleAnimation() {
mBubbleScaleAnimation.cancel();
mBubbleScaleAnimation.setFloatValues(mDrawBubbleScale, 1.0f);
mBubbleScaleAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
mBubbleScaleAnimation.setDuration((long) (300 * Math.abs(mDrawBubbleScale - 1.0f)));
mBubbleScaleAnimation.start();
}
private void startHideBubbleAnimation() {
mBubbleScaleAnimation.cancel();
mBubbleScaleAnimation.setFloatValues(mDrawBubbleScale, 0.0f);
mBubbleScaleAnimation.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR);
mBubbleScaleAnimation.setDuration((long) (300 * Math.abs(mDrawBubbleScale - 0.0f)));
mBubbleScaleAnimation.start();
}
public void setColor(int color) {
mPaint.setColor(color);
mBubble.setColor(color);
invalidate();
}
public void setRange(int start, int end) {
mStart = start;
mEnd = end;
setProgress(mProgress);
updateBubbleSize();
}
public void setProgress(int progress) {
progress = MathUtils.clamp(progress, mStart, mEnd);
if (mProgress != progress) {
int oldProgress = mProgress;
mProgress = progress;
mPercent = MathUtils.delerp(mStart, mEnd, mProgress);
mTargetProgress = progress;
if (mProgressAnimation == null) {
// For init
mDrawPercent = mPercent;
mDrawProgress = mProgress;
updateBubblePosition();
mBubble.setProgress(mDrawProgress);
} else {
startProgressAnimation(mPercent);
}
if (mListener != null) {
mListener.onSetProgress(this, progress, oldProgress, false, true);
}
invalidate();
}
}
public int getProgress() {
return mProgress;
}
public void setReverse(boolean reverse) {
if (mReverse != reverse) {
mReverse = reverse;
invalidate();
}
}
public void setOnSetProgressListener(OnSetProgressListener listener) {
mListener = listener;
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
int width = getWidth();
int height = getHeight();
if (width < LayoutUtils.dp2pix(mContext, 24)) {
canvas.drawRect(0, 0, width, getHeight(), mPaint);
} else {
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
float thickness = mThickness;
float radius = mRadius;
float halfThickness = thickness / 2;
int saved = canvas.save();
canvas.translate(0, paddingTop + ((height - paddingTop - paddingBottom) / 2));
float currentX = paddingLeft + radius + (width - radius - radius - paddingLeft - paddingRight) *
(mReverse ? (1.0f - mDrawPercent) : mDrawPercent);
mLeftRectF.set(paddingLeft + radius, -halfThickness, currentX, halfThickness);
mRightRectF.set(currentX, -halfThickness, width - paddingRight - radius, halfThickness);
// Draw bar
if (mReverse) {
canvas.drawRect(mRightRectF, mPaint);
canvas.drawRect(mLeftRectF, mBgPaint);
} else {
canvas.drawRect(mLeftRectF, mPaint);
canvas.drawRect(mRightRectF, mBgPaint);
}
// Draw controller
float scale = 1.0f - mDrawBubbleScale;
if (scale != 0.0f) {
canvas.scale(scale, scale, currentX, 0);
canvas.drawCircle(currentX, 0, radius, mPaint);
}
canvas.restoreToCount(saved);
}
}
private void setShowBubble(boolean showBubble) {
if (mShowBubble != showBubble) {
mShowBubble = showBubble;
if (showBubble) {
startShowBubbleAnimation();
} else {
startHideBubbleAnimation();
}
}
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
float radius = mRadius;
float x = event.getX();
int progress = Math.round(MathUtils.lerp((float) mStart, (float) mEnd,
MathUtils.clamp((mReverse ? (getWidth() - paddingLeft - radius - x) : (x - radius - paddingLeft)) /
(getWidth() - radius - radius - paddingLeft - paddingRight), 0.0f, 1.0f)));
float percent = MathUtils.delerp(mStart, mEnd, progress);
// ACTION_CANCEL not changed
if (action == MotionEvent.ACTION_CANCEL) {
progress = mProgress;
percent = mPercent;
}
if (mTargetProgress != progress) {
mTargetProgress = progress;
startProgressAnimation(percent);
if (mListener != null) {
mListener.onSetProgress(this, progress, progress, true, false);
}
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
SimpleHandler.getInstance().removeCallbacks(mCheckForShowBubble);
setShowBubble(false);
} else if (action == MotionEvent.ACTION_DOWN) {
if (mCheckForShowBubble == null) {
mCheckForShowBubble = new CheckForShowBubble();
}
SimpleHandler.getInstance().postDelayed(mCheckForShowBubble, ViewConfiguration.getTapTimeout());
}
if (action == MotionEvent.ACTION_UP) {
if (mProgress != progress) {
int oldProgress = mProgress;
mProgress = progress;
mPercent = mDrawPercent;
if (mListener != null) {
mListener.onSetProgress(this, progress, oldProgress, true, true);
}
}
}
break;
}
return true;
}
@SuppressLint("ViewConstructor")
private static class BubbleView extends View {
private static final float TEXT_CENTER = (float) BUBBLE_WIDTH / 2.0f / BUBBLE_HEIGHT;
private VectorDrawable mVectorDrawable;
private Paint mTextPaint;
private String mProgressStr = "";
private Rect mRect = new Rect();
@SuppressWarnings("deprecation")
public BubbleView(Context context, Paint paint) {
super(context);
mVectorDrawable = VectorDrawable.create(context, R.drawable.slider_bubble);
setBackgroundDrawable(mVectorDrawable);
mTextPaint = paint;
}
public void setColor(int color) {
Object obj = mVectorDrawable.getTargetByName("bubble");
if (obj instanceof VectorDrawable.VFullPath) {
((VectorDrawable.VFullPath) obj).setFillColor(color);
}
}
public void setProgress(int progress) {
String str = Integer.toString(progress);
if (!str.equals(mProgressStr)) {
mProgressStr = str;
mTextPaint.getTextBounds(str, 0, str.length(), mRect);
invalidate();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setPivotX(w / 2);
setPivotY(h);
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
int width = getWidth();
int height = getHeight();
int x = width / 2;
int y = (int) ((height * TEXT_CENTER) + (mRect.height() / 2));
canvas.drawText(mProgressStr, x, y, mTextPaint);
}
}
public interface OnSetProgressListener {
void onSetProgress(Slider slider, int newProgress, int oldProgress, boolean byUser, boolean confirm);
}
private final class CheckForShowBubble implements Runnable {
@Override
public void run() {
setShowBubble(true);
}
}
}