diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index acec839ae..c787d68c6 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -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 mConaco; private LruCacheEx mGalleryDetailCache; + private LruCacheEx mLargePreviewSetCache; + private LruCacheEx mPreviewPagesCache; @Override public void onCreate() { @@ -179,4 +182,34 @@ public class EhApplication extends SceneApplication { } return application.mGalleryDetailCache; } + + @NonNull + public static LruCacheEx 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() { + @Override + public int compare(String lhs, String rhs) { + return lhs.compareTo(rhs); + } + }); + } + return application.mLargePreviewSetCache; + } + + @NonNull + public static LruCacheEx 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() { + @Override + public int compare(Integer lhs, Integer rhs) { + return lhs - rhs; + } + }); + } + return application.mPreviewPagesCache; + } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhCacheKeyFactory.java b/app/src/main/java/com/hippo/ehviewer/client/EhCacheKeyFactory.java index 14e93496a..b48fa786d 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhCacheKeyFactory.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhCacheKeyFactory.java @@ -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; } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java index 5b6eb0242..84c47b199 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -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); } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java index a062c6977..3bb0a05a2 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -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 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; + } + } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java b/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java index 1de9b8409..bc0c779df 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java @@ -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(); } diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryPreview.java b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryPreview.java new file mode 100644 index 000000000..906fcf06a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryPreview.java @@ -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 CREATOR = new Parcelable.Creator() { + public GalleryPreview createFromParcel(Parcel source) { + return new GalleryPreview(source); + } + + public GalleryPreview[] newArray(int size) { + return new GalleryPreview[size]; + } + }; +} diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.java b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.java index ff033a2d7..b0917b15d 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.java +++ b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.java @@ -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(); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java index 9990ec57f..e6b57d3cb 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java @@ -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(); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.java new file mode 100644 index 000000000..51c9bbda8 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.java @@ -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 { + + @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 { + + @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 result, int taskId) { + if (mHelper != null && mHelper.isCurrentTask(taskId)) { + LargePreviewSet previewSet = result.first; + int size = previewSet.size(); + ArrayList 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> { + + 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 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; + } + } +} diff --git a/app/src/main/java/com/hippo/widget/Slider.java b/app/src/main/java/com/hippo/widget/Slider.java new file mode 100644 index 000000000..b545bfe0a --- /dev/null +++ b/app/src/main/java/com/hippo/widget/Slider.java @@ -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); + } + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_go_to_dark_x24.png b/app/src/main/res/drawable-hdpi/ic_go_to_dark_x24.png new file mode 100644 index 000000000..84a8b1000 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_go_to_dark_x24.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_go_to_dark_x24.png b/app/src/main/res/drawable-mdpi/ic_go_to_dark_x24.png new file mode 100644 index 000000000..1b69918a3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_go_to_dark_x24.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_go_to_dark_x24.png b/app/src/main/res/drawable-xhdpi/ic_go_to_dark_x24.png new file mode 100644 index 000000000..a4f4e05dc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_go_to_dark_x24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_go_to_dark_x24.png b/app/src/main/res/drawable-xxhdpi/ic_go_to_dark_x24.png new file mode 100644 index 000000000..ba8cb638a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_go_to_dark_x24.png differ diff --git a/app/src/main/res/drawable/slider_bubble.xml b/app/src/main/res/drawable/slider_bubble.xml new file mode 100644 index 000000000..3ea98710c --- /dev/null +++ b/app/src/main/res/drawable/slider_bubble.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/layout/dialog_go_to.xml b/app/src/main/res/layout/dialog_go_to.xml new file mode 100644 index 000000000..756410e52 --- /dev/null +++ b/app/src/main/res/layout/dialog_go_to.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_preview.xml b/app/src/main/res/layout/item_gallery_preview.xml similarity index 100% rename from app/src/main/res/layout/item_preview.xml rename to app/src/main/res/layout/item_gallery_preview.xml diff --git a/app/src/main/res/layout/scene_gallery_previews.xml b/app/src/main/res/layout/scene_gallery_previews.xml new file mode 100644 index 000000000..d8a09920a --- /dev/null +++ b/app/src/main/res/layout/scene_gallery_previews.xml @@ -0,0 +1,22 @@ + + + + diff --git a/app/src/main/res/menu/gallery_detail_other_action.xml b/app/src/main/res/menu/gallery_detail.xml similarity index 100% rename from app/src/main/res/menu/gallery_detail_other_action.xml rename to app/src/main/res/menu/gallery_detail.xml diff --git a/app/src/main/res/menu/gallery_previews.xml b/app/src/main/res/menu/gallery_previews.xml new file mode 100644 index 000000000..30e264156 --- /dev/null +++ b/app/src/main/res/menu/gallery_previews.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 5440ed767..5b70866f8 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -76,4 +76,16 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values/pathdata.xml b/app/src/main/res/values/pathdata.xml index 45e70004c..d15c5a38d 100644 --- a/app/src/main/res/values/pathdata.xml +++ b/app/src/main/res/values/pathdata.xml @@ -27,4 +27,6 @@ M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z M10,9V5L3,12L10,19V14.9C15,14.9 18.5,16.5 21,20C20,15 17,10 10,9Z + M13,0C20.2,0,26,5.9,26,13.2,26,16.9,24,20.8,18.7,26.1L13,32,7.2,26.2C2,20.8,0,16.9,0,13.3,0,5.9,5.8,0,13,0Z + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b4996af1..62a2cb62f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -124,7 +124,7 @@ 5 stars - Can\'t find the gallery + Can\'t find the gallery Read More information @@ -192,6 +192,11 @@ Gallery Comments + + Gallery Previews + Go to + Out of range + From the future Just now diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index fceb9c17b..9b7cc7519 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -102,4 +102,13 @@ ?attr/colorPrimary + +