diff --git a/app/build.gradle b/app/build.gradle index b813cf881..7f319e320 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -72,6 +72,7 @@ dependencies { compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:customtabs:23.1.1' compile 'com.android.support:design:23.1.1' + compile 'com.h6ah4i.android.materialshadowninepatch:materialshadowninepatch:0.6.5' compile 'de.greenrobot:greendao:2.0.0' compile 'org.ccil.cowan.tagsoup:tagsoup:1.2.1' } diff --git a/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java b/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java new file mode 100644 index 000000000..ce6860841 --- /dev/null +++ b/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java @@ -0,0 +1,141 @@ +/* + * 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.drawable; + +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.widget.TextView; + +import com.hippo.conaco.Conaco; +import com.hippo.conaco.Unikery; +import com.hippo.conaco.ValueHolder; + +public class UnikeryDrawable extends WrapDrawable implements Unikery { + + private int mTaskId = Unikery.INVALID_ID; + + private TextView mTextView; + + private ValueHolder mHolder; + + public UnikeryDrawable(TextView textView) { + mTextView = textView; + } + + @Override + public void setDrawable(Drawable drawable) { + // Remove old callback + Drawable oldDrawable = getDrawable(); + if (oldDrawable != null) { + oldDrawable.setCallback(null); + } + + super.setDrawable(drawable); + + if (drawable != null) { + drawable.setCallback(mTextView); + } + + updateBounds(); + if (drawable != null) { + // mTextView.requestLayout() not work! + CharSequence cs = mTextView.getText(); + mTextView.setText(cs); + } + } + + @Override + public void setTaskId(int id) { + mTaskId = id; + } + + @Override + public int getTaskId() { + return mTaskId; + } + + @Override + public void onMiss(Conaco.Source source) { + } + + @Override + public void onRequest() { + } + + @Override + public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) { + } + + private void removeDrawableAndHolder() { + // Remove drawable + Drawable drawable = getDrawable(); + if (drawable instanceof ImageDrawable) { + ((ImageDrawable) drawable).recycle(); + } + setDrawable(null); + + // Remove holder + if (mHolder != null) { + mHolder.release(this); + + ImageWrapper imageWrapper = mHolder.getValue(); + if (mHolder.isFree()) { + // ImageWrapper is free, stop animate + imageWrapper.stop(); + if (!mHolder.isInMemoryCache()) { + // ImageWrapper is not needed any more, recycle it + imageWrapper.recycle(); + } + } + + mHolder = null; + } + } + + @Override + public boolean onGetObject(@NonNull ValueHolder holder, Conaco.Source source) { + holder.obtain(this); + + removeDrawableAndHolder(); + + mHolder = holder; + ImageWrapper imageWrapper = holder.getValue(); + Drawable drawable = new ImageDrawable(imageWrapper); + imageWrapper.start(); + + setDrawable(drawable); + + return true; + } + + @Override + public void onSetDrawable(Drawable drawable) { + removeDrawableAndHolder(); + + setDrawable(drawable); + } + + @Override + public void onFailure() { + // Empty + } + + @Override + public void onCancel() { + // Empty + } +} diff --git a/app/src/main/java/com/hippo/drawable/WrapDrawable.java b/app/src/main/java/com/hippo/drawable/WrapDrawable.java new file mode 100644 index 000000000..6d031ac6b --- /dev/null +++ b/app/src/main/java/com/hippo/drawable/WrapDrawable.java @@ -0,0 +1,143 @@ +/* + * 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.drawable; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +public class WrapDrawable extends Drawable { + + private Drawable mDrawable; + + public void setDrawable(Drawable drawable) { + mDrawable = drawable; + } + + public Drawable getDrawable() { + return mDrawable; + } + + public void updateBounds() { + setBounds(0, 0, getIntrinsicWidth(), getIntrinsicHeight()); + } + + @Override + public void draw(Canvas canvas) { + if (mDrawable != null) { + mDrawable.draw(canvas); + } + } + + @Override + public void setBounds(int left, int top, int right, int bottom) { + super.setBounds(left, top, right, bottom); + + if (mDrawable != null) { + mDrawable.setBounds(left, top, right, bottom); + } + } + + @Override + public void setBounds(Rect bounds) { + super.setBounds(bounds); + + if (mDrawable != null) { + mDrawable.setBounds(bounds); + } + } + + @Override + public void setChangingConfigurations(int configs) { + super.setChangingConfigurations(configs); + + if (mDrawable != null) { + mDrawable.setChangingConfigurations(configs); + } + } + + @Override + public int getChangingConfigurations() { + if (mDrawable != null) { + return mDrawable.getChangingConfigurations(); + } else { + return super.getChangingConfigurations(); + } + } + + @Override + public void setDither(boolean dither) { + super.setDither(dither); + + if (mDrawable != null) { + mDrawable.setDither(dither); + } + } + + @Override + public void setFilterBitmap(boolean filter) { + super.setFilterBitmap(filter); + + if (mDrawable != null) { + mDrawable.setFilterBitmap(filter); + } + } + + + @Override + public void setAlpha(int alpha) { + if (mDrawable != null) { + mDrawable.setAlpha(alpha); + } + } + + @Override + public void setColorFilter(ColorFilter cf) { + if (mDrawable != null) { + mDrawable.setColorFilter(cf); + } + } + + @Override + public int getOpacity() { + if (mDrawable != null) { + return mDrawable.getOpacity(); + } else { + return PixelFormat.UNKNOWN; + } + } + + @Override + public int getIntrinsicWidth() { + if (mDrawable != null) { + return mDrawable.getIntrinsicWidth(); + } else { + return super.getIntrinsicWidth(); + } + } + + @Override + public int getIntrinsicHeight() { + if (mDrawable != null) { + return mDrawable.getIntrinsicHeight(); + } else { + return super.getIntrinsicHeight(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index 61a0f8c9b..6666dbe7c 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -21,7 +21,7 @@ import android.content.Context; import android.support.annotation.NonNull; import android.util.SparseArray; -import com.hippo.beerbelly.LruMap; +import com.hippo.beerbelly.LruCacheEx; import com.hippo.conaco.Conaco; import com.hippo.drawable.ImageWrapper; import com.hippo.ehviewer.client.EhClient; @@ -30,6 +30,7 @@ import com.hippo.ehviewer.client.data.GalleryDetail; import com.hippo.network.StatusCodeException; import com.hippo.okhttp.CookieDB; import com.hippo.scene.SceneApplication; +import com.hippo.text.Html; import com.hippo.utils.ReadableTime; import com.hippo.yorozuya.IntIdGenerator; @@ -41,6 +42,8 @@ import okhttp3.OkHttpClient; public class EhApplication extends SceneApplication { + private static final boolean DEBUG_CONACO = false; + private IntIdGenerator mIdGenerator = new IntIdGenerator(); private SparseArray mGlobalStuffMap = new SparseArray<>(); private EhCookieStore mEhCookieStore; @@ -48,7 +51,7 @@ public class EhApplication extends SceneApplication { private OkHttpClient mOkHttpClient; private ImageWrapperHelper mImageWrapperHelper; private Conaco mConaco; - private LruMap mGalleryDetailCache; + private LruCacheEx mGalleryDetailCache; @Override public void onCreate() { @@ -59,6 +62,7 @@ public class EhApplication extends SceneApplication { StatusCodeException.initialize(this); Settings.initialize(this); ReadableTime.initialize(this); + Html.initialize(this); } public int putGlobalStuff(@NonNull Object o) { @@ -155,22 +159,23 @@ public class EhApplication extends SceneApplication { builder.diskCacheMaxSize = 80 * 1024 * 1024; // 80MB builder.okHttpClient = getOkHttpClient(context); builder.objectHelper = getImageWrapperHelper(context); - builder.debug = BuildConfig.DEBUG; + builder.debug = DEBUG_CONACO; application.mConaco = builder.build(); } return application.mConaco; } @NonNull - public static LruMap getGalleryDetailCache(@NonNull Context context) { + public static LruCacheEx getGalleryDetailCache(@NonNull Context context) { EhApplication application = ((EhApplication) context.getApplicationContext()); if (application.mGalleryDetailCache == null) { - application.mGalleryDetailCache = new LruMap<>(new Comparator() { + // Max size 25, 3 min timeout + application.mGalleryDetailCache = new LruCacheEx<>(25, 3 * 60 * 1000, new Comparator() { @Override public int compare(Integer lhs, Integer rhs) { return lhs - rhs; } - }, 3 * 60 * 1000); // 3 min timeout + }); } return application.mGalleryDetailCache; } diff --git a/app/src/main/java/com/hippo/ehviewer/EhCacheKeyFactory.java b/app/src/main/java/com/hippo/ehviewer/EhCacheKeyFactory.java deleted file mode 100644 index 8f2e21212..000000000 --- a/app/src/main/java/com/hippo/ehviewer/EhCacheKeyFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.hippo.ehviewer; - -public class EhCacheKeyFactory { - - public static String getThumbKey(int gid) { - 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 getImageKey(int gid, int index) { - return "image:" + gid + ":" + index; - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhCacheKeyFactory.java b/app/src/main/java/com/hippo/ehviewer/client/EhCacheKeyFactory.java new file mode 100644 index 000000000..14e93496a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/EhCacheKeyFactory.java @@ -0,0 +1,42 @@ +/* + * 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; + +public class EhCacheKeyFactory { + + public static String getThumbKey(int gid) { + 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 getImageKey(int gid, int index) { + return "image:" + gid + ":" + index; + } +} 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 c48649dca..a062c6977 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -19,6 +19,7 @@ package com.hippo.ehviewer.client; import android.support.annotation.Nullable; import android.util.Log; +import com.hippo.ehviewer.client.data.GalleryDetail; import com.hippo.ehviewer.client.exception.CancelledException; import com.hippo.ehviewer.client.exception.EhException; import com.hippo.ehviewer.client.parser.GalleryDetailParser; @@ -126,7 +127,7 @@ public class EhEngine { return okHttpClient.newCall(request); } - public static GalleryDetailParser.Result doGetGalleryDetail(Call call) throws Exception { + public static GalleryDetail doGetGalleryDetail(Call call) throws Exception { String body = null; Headers headers = null; int code = -1; diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryDetail.java b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryDetail.java index 5009978d5..9dfcb7070 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryDetail.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryDetail.java @@ -34,6 +34,7 @@ public class GalleryDetail extends GalleryInfo { public GalleryTagGroup[] tags; public GalleryComment[] comments; public int previewPages; + public LargePreviewSet previewSet; @Override @@ -58,6 +59,7 @@ public class GalleryDetail extends GalleryInfo { dest.writeParcelableArray(this.tags, 0); dest.writeParcelableArray(this.comments, 0); dest.writeInt(this.previewPages); + dest.writeParcelable(previewSet, flags); } public GalleryDetail() { @@ -79,6 +81,7 @@ public class GalleryDetail extends GalleryInfo { this.tags = in.createTypedArray(GalleryTagGroup.CREATOR); this.comments = in.createTypedArray(GalleryComment.CREATOR); this.previewPages = in.readInt(); + this.previewSet = in.readParcelable(LargePreviewSet.class.getClassLoader()); } public static final Creator CREATOR = new Creator() { diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/LargePreviewSet.java b/app/src/main/java/com/hippo/ehviewer/client/data/LargePreviewSet.java index eca38aa8a..6bf14a191 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/data/LargePreviewSet.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/LargePreviewSet.java @@ -16,10 +16,16 @@ package com.hippo.ehviewer.client.data; +import android.os.Parcel; +import android.os.Parcelable; + +import com.hippo.yorozuya.IntList; + import java.util.ArrayList; -public class LargePreviewSet { +public class LargePreviewSet implements Parcelable { + private IntList mIndexList = new IntList(); private ArrayList mImageUrlList = new ArrayList<>(); private ArrayList mPageUrlList = new ArrayList<>(); @@ -27,11 +33,16 @@ public class LargePreviewSet { return mImageUrlList.size(); } - public void addItem(String imageUrl, String pageUrl) { + public void addItem(int index, String imageUrl, String pageUrl) { + mIndexList.add(index); mImageUrlList.add(imageUrl); mPageUrlList.add(pageUrl); } + public int getIndexAt(int index) { + return mIndexList.get(index); + } + public String getImageUrlAt(int index) { return mImageUrlList.get(index); } @@ -39,4 +50,36 @@ public class LargePreviewSet { public String getPageUrlAt(int index) { return mPageUrlList.get(index); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + this.mIndexList.writeToParcel(dest, flags); + dest.writeParcelable(this.mIndexList, 0); + dest.writeStringList(this.mImageUrlList); + dest.writeStringList(this.mPageUrlList); + } + + public LargePreviewSet() { + } + + protected LargePreviewSet(Parcel in) { + this.mIndexList = IntList.CREATOR.createFromParcel(in); + this.mImageUrlList = in.createStringArrayList(); + this.mPageUrlList = in.createStringArrayList(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public LargePreviewSet createFromParcel(Parcel source) { + return new LargePreviewSet(source); + } + + public LargePreviewSet[] newArray(int size) { + return new LargePreviewSet[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 9b49a3855..8ef97576b 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 @@ -81,7 +81,7 @@ public class GalleryDetailParser { public static final Pattern PAGES_PATTERN = Pattern.compile("]*>Length:]*>([\\d,]+) pages"); public static final Pattern PREVIEW_PAGES_PATTERN = Pattern.compile("]+>]+>([\\d,]+)]+>(?:]+>)?>(?:)?"); private static final Pattern NORMAL_PREVIEW_PATTERN = Pattern.compile("]*class=\"gdtm\"[^<>]*>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*>]*href=\"(.+?)\"[^<>]*>"); - private static final Pattern LARGE_PREVIEW_PATTERN = Pattern.compile("
\"([\\d,]+)\".+?src=\"(.+?)\""); { + + @Override + public CommentHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new CommentHolder(getActivity().getLayoutInflater() + .inflate(R.layout.item_gallery_comment, parent, false)); + } + + @Override + public void onBindViewHolder(CommentHolder holder, int position) { + GalleryComment comment = mComments[position]; + holder.user.setText(comment.user); + holder.time.setText(ReadableTime.getTimeAgo(comment.time)); + holder.comment.setText(TextUrl.handleTextUrl(Html.fromHtml(comment.comment, + new URLImageGetter(holder.comment, EhApplication.getConaco(getContext())), null))); + } + + @Override + public int getItemCount() { + return mComments == null ? 0 : mComments.length; + } + } +} 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 4a75876af..bf3663576 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 @@ -24,6 +24,7 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.PopupMenu; import android.text.TextUtils; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -34,8 +35,8 @@ import android.widget.ScrollView; import android.widget.TextView; import com.hippo.ehviewer.EhApplication; -import com.hippo.ehviewer.EhCacheKeyFactory; 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; @@ -44,19 +45,25 @@ import com.hippo.ehviewer.client.data.GalleryComment; import com.hippo.ehviewer.client.data.GalleryDetail; import com.hippo.ehviewer.client.data.GalleryInfo; import com.hippo.ehviewer.client.data.GalleryTagGroup; -import com.hippo.ehviewer.client.parser.GalleryDetailParser; +import com.hippo.ehviewer.client.data.LargePreviewSet; +import com.hippo.ehviewer.client.data.ListUrlBuilder; import com.hippo.rippleold.RippleSalon; import com.hippo.scene.SceneFragment; import com.hippo.scene.StageActivity; import com.hippo.text.Html; +import com.hippo.text.URLImageGetter; import com.hippo.utils.ApiHelper; +import com.hippo.utils.ExceptionUtils; import com.hippo.utils.ReadableTime; import com.hippo.vector.AnimatedVectorDrawable; import com.hippo.vector.VectorDrawable; import com.hippo.widget.AutoWrapLayout; import com.hippo.widget.LoadImageView; +import com.hippo.widget.SimpleGridLayout; import com.hippo.widget.SimpleImageView; +import java.util.Locale; + // TODO Update drawer checked item public class GalleryDetailScene extends BaseScene implements View.OnClickListener { @@ -111,8 +118,10 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene // Comments private LinearLayout mComments; private TextView mCommentsText; - - + // Previews + private View mPreviews; + private SimpleGridLayout mGridLayout; + private TextView mPreviewText; // Progress private View mProgress; @@ -175,6 +184,27 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene } } + private String getUploader() { + if (mGalleryDetail != null) { + return mGalleryDetail.uploader; + } else if (mGalleryInfo != null) { + return mGalleryInfo.uploader; + } else { + return null; + } + } + + // -1 for error + private int getCategory() { + if (mGalleryDetail != null) { + return mGalleryDetail.category; + } else if (mGalleryInfo != null) { + return mGalleryInfo.category; + } else { + return -1; + } + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -199,6 +229,26 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene mRequestId = savedInstanceState.getInt(KEY_REQUEST_ID); } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + if (mAction != null) { + outState.putString(KEY_ACTION, mAction); + } + if (mGalleryInfo != null) { + outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo); + } + outState.putInt(KEY_GID, mGid); + if (mToken != null) { + outState.putString(KEY_TOKEN, mAction); + } + if (mGalleryDetail != null) { + outState.putParcelable(KEY_GALLERY_DETAIL, mGalleryDetail); + } + outState.putInt(KEY_REQUEST_ID, mRequestId); + } + @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @@ -210,6 +260,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene mProgressView = main.findViewById(R.id.progress_view); mFailedView = (ViewGroup) main.findViewById(R.id.tip); mFailedText = (TextView) mFailedView.getChildAt(1); + mFailedView.setOnClickListener(this); View mainView = mMainView; mHeader = mainView.findViewById(R.id.header); @@ -225,6 +276,8 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene RippleSalon.addRipple(mOtherActions, false); RippleSalon.addRipple(mDownload, false); RippleSalon.addRipple(mRead, false); + mUploader.setOnClickListener(this); + mCategory.setOnClickListener(this); mOtherActions.setOnClickListener(this); mDownload.setOnClickListener(this); mRead.setOnClickListener(this); @@ -265,16 +318,24 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene mComments = (LinearLayout) belowHeader.findViewById(R.id.comments); mCommentsText = (TextView) mComments.findViewById(R.id.comments_text); + mPreviews = belowHeader.findViewById(R.id.previews); + mGridLayout = (SimpleGridLayout) mPreviews.findViewById(R.id.grid_layout); + mPreviewText = (TextView) mPreviews.findViewById(R.id.preview_text); + mProgress = mainView.findViewById(R.id.progress); - fetchGalleryDetail(); - adjustViewVisibility(); - if (mGalleryDetail == null) { - bindViewFirst(); + if (prepareData()) { + if (mGalleryDetail != null) { + bindViewSecond(); + setTransitionName(); + } else if (mGalleryInfo != null) { + bindViewFirst(); + setTransitionName(); + } } else { - bindViewSecond(); + mFailedText.setText(R.string.cannot_find_gallery); } - setTransitionName(); + adjustViewVisibility(); return view; } @@ -316,6 +377,16 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene mShare = null; mRate = null; + mTags = null; + mNoTags = null; + + mComments = null; + mCommentsText = null; + + mPreviews = null; + mGridLayout = null; + mPreviewText = null; + mProgress = null; mHeartDrawable = null; @@ -323,32 +394,36 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene mPopupMenu = null; } - private void fetchGalleryDetail() { + private boolean prepareData() { if (mGalleryDetail != null) { - return; + return true; } int gid = getGid(); if (gid == -1) { - return; + return false; } // Get from cache mGalleryDetail = EhApplication.getGalleryDetailCache(getContext()).get(gid); if (mGalleryDetail != null) { - return; + return true; } EhApplication application = (EhApplication) getContext().getApplicationContext(); if (application.containGlobalStuff(mRequestId)) { // request exist - return; + return true; } // Do request + return request(); + } + + private boolean request() { String url = getGalleryDetailUrl(); if (url == null) { - return; + return false; } EhClient.Callback callback = new GetGalleryDetailListener(getContext(), @@ -359,6 +434,8 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene .setArgs(url) .setCallback(callback); EhApplication.getEhClient(getContext()).execute(request); + + return true; } private void adjustViewVisibility() { @@ -400,9 +477,6 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene mUploader.setText(gi.uploader); mCategory.setText(EhUtils.getCategory(gi.category)); mCategory.setTextColor(EhUtils.getCategoryColor(gi.category)); - - // Hide actions - mActionGroup.setVisibility(View.GONE); } } @@ -435,10 +509,6 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene return; } - if (mActionGroup.getVisibility() == View.GONE) { - mActionGroup.setVisibility(View.VISIBLE); - } - Resources resources = getContext().getResources(); if (TextUtils.isEmpty(mCategory.getText())) { @@ -471,8 +541,10 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene bindTags(gd.tags); bindComments(gd.comments); + bindPreviews(gd); } + @SuppressWarnings("deprecation") private void bindTags(GalleryTagGroup[] tagGroups) { mTags.removeViews(1, mTags.getChildCount() - 1); if (tagGroups == null || tagGroups.length == 0) { @@ -502,13 +574,19 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene for (int j = 0, z = tg.size(); j < z; j++) { TextView tag = (TextView) inflater.inflate(R.layout.item_gallery_tag, awl, false); awl.addView(tag); - tag.setText(tg.getTagAt(j)); + String tagStr = tg.getTagAt(j); + tag.setText(tagStr); tag.setBackgroundColor(colorTag); + tag.setTag(R.id.tag, tagStr); + tag.setOnClickListener(this); } } } private void bindComments(GalleryComment[] comments) { + RippleSalon.addRipple(mComments, false); + mComments.setOnClickListener(this); + mComments.removeViews(0, mComments.getChildCount() - 1); final int maxShowCount = 2; @@ -527,9 +605,40 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene GalleryComment comment = comments[i]; View v = inflater.inflate(R.layout.item_gallery_comment, mComments, false); mComments.addView(v, i); - ((TextView) v.findViewById(R.id.user)).setText(comment.user); - ((TextView) v.findViewById(R.id.time)).setText(ReadableTime.getTimeAgo(comment.time)); - ((TextView) v.findViewById(R.id.comment)).setText(Html.fromHtml(comment.comment)); + TextView user = (TextView) v.findViewById(R.id.user); + user.setText(comment.user); + TextView time = (TextView) v.findViewById(R.id.time); + time.setText(ReadableTime.getTimeAgo(comment.time)); + TextView c = (TextView) v.findViewById(R.id.comment); + c.setMaxLines(5); + c.setText(Html.fromHtml(comment.comment, + new URLImageGetter(c, EhApplication.getConaco(getContext())), null)); + } + } + + private void bindPreviews(GalleryDetail gd) { + mGridLayout.removeAllViews(); + LargePreviewSet previewSet = gd.previewSet; + if (gd.previewPages <= 0 || previewSet == null || previewSet.size() == 0) { + mPreviewText.setText(R.string.no_previews); + return; + } else if (gd.previewPages == 1) { + mPreviewText.setText(R.string.no_more_previews); + } else { + mPreviewText.setText(R.string.more_previews); + } + + LayoutInflater inflater = getActivity().getLayoutInflater(); + for (int i = 0, size = previewSet.size(); i < size; i++) { + View view = inflater.inflate(R.layout.item_preview, mGridLayout, false); + mGridLayout.addView(view); + + int index = previewSet.getIndexAt(i); + String imageUrl = previewSet.getImageUrlAt(i); + LoadImageView image = (LoadImageView) view.findViewById(R.id.image); + image.load(EhCacheKeyFactory.getLargePreviewKey(gd.gid, index), imageUrl, true); + TextView text = (TextView) view.findViewById(R.id.text); + text.setText(String.format(Locale.US, "%d", index)); } } @@ -589,32 +698,12 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene } } - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - if (mAction != null) { - outState.putString(KEY_ACTION, mAction); - } - if (mGalleryInfo != null) { - outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo); - } - outState.putInt(KEY_GID, mGid); - if (mToken != null) { - outState.putString(KEY_TOKEN, mAction); - } - if (mGalleryDetail != null) { - outState.putParcelable(KEY_GALLERY_DETAIL, mGalleryDetail); - } - outState.putInt(KEY_REQUEST_ID, mRequestId); - } - private void ensurePopMenu() { if (mPopupMenu != null) { return; } - PopupMenu popup = new PopupMenu(getContext(), mOtherActions); + PopupMenu popup = new PopupMenu(getContext(), mOtherActions, Gravity.TOP); mPopupMenu = popup; popup.getMenuInflater().inflate(R.menu.gallery_detail_other_action, popup.getMenu()); popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @@ -632,23 +721,65 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene @Override public void onClick(View v) { - if (mOtherActions == v) { + if (mFailedView == v) { + if (request()) { + mFailedText.setText(""); + adjustViewVisibility(); + } + } else if (mOtherActions == v) { ensurePopMenu(); mPopupMenu.show(); + } else if (mUploader == v) { + String uploader = getUploader(); + if (TextUtils.isEmpty(uploader)) { + return; + } + ListUrlBuilder lub = new ListUrlBuilder(); + lub.setMode(ListUrlBuilder.MODE_UPLOADER); + lub.setKeyword(EhUtils.getSpecifyUploaderKeyword(uploader)); + GalleryListScene.startScene(this, lub); + } else if (mCategory == v) { + int category = getCategory(); + if (category == -1) { + return; + } + ListUrlBuilder lub = new ListUrlBuilder(); + lub.setCategory(category); + GalleryListScene.startScene(this, lub); + } else if (mInfo == v) { + Bundle args = new Bundle(); + args.putParcelable(GalleryInfoScene.KEY_GALLERY_DETAIL, mGalleryDetail); + startScene(GalleryInfoScene.class, args); + } else if (mComments == v) { + Bundle args = new Bundle(); + args.putParcelableArray(GalleryCommentsScene.KEY_COMMENTS, mGalleryDetail.comments); + startScene(GalleryCommentsScene.class, args); + } else { + Object tag; + tag = v.getTag(R.id.tag); + if (tag instanceof String) { + String tagStr = (String) tag; + ListUrlBuilder lub = new ListUrlBuilder(); + lub.setMode(ListUrlBuilder.MODE_TAG); + lub.setKeyword(tagStr); + GalleryListScene.startScene(this, lub); + } } } - private void onGetGalleryDetailSuccess(GalleryDetailParser.Result result) { - mGalleryDetail = result.galleryDetail; + private void onGetGalleryDetailSuccess(GalleryDetail result) { + mGalleryDetail = result; adjustViewVisibility(); bindViewSecond(); } private void onGetGalleryDetailFailure(Exception e) { - + String error = ExceptionUtils.getReadableString(getContext(), e); + mFailedText.setText(error); + adjustViewVisibility(); } - private static class GetGalleryDetailListener implements EhClient.Callback { + private static class GetGalleryDetailListener implements EhClient.Callback { private EhApplication mApplication; private int mStageId; @@ -674,12 +805,11 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene } @Override - public void onSuccess(GalleryDetailParser.Result result) { + public void onSuccess(GalleryDetail result) { mApplication.removeGlobalStuff(this); // Put gallery detail to cache - GalleryDetail gd = result.galleryDetail; - EhApplication.getGalleryDetailCache(mApplication).put(gd.gid, gd); + EhApplication.getGalleryDetailCache(mApplication).put(result.gid, result); // Notify success GalleryDetailScene scene = getScene(); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryInfoScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryInfoScene.java new file mode 100644 index 000000000..0a86c5934 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryInfoScene.java @@ -0,0 +1,231 @@ +/* + * 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.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.res.Resources; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +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.LinearDividerItemDecoration; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.client.EhUrl; +import com.hippo.ehviewer.client.EhUtils; +import com.hippo.ehviewer.client.data.GalleryDetail; +import com.hippo.rippleold.RippleSalon; +import com.hippo.vector.VectorDrawable; +import com.hippo.yorozuya.LayoutUtils; + +import java.util.ArrayList; + +public final class GalleryInfoScene extends ToolbarScene implements EasyRecyclerView.OnItemClickListener { + + public static final String KEY_GALLERY_DETAIL = "gallery_detail"; + public static final String KEY_KEYS = "keys"; + public static final String KEY_VALUES = "values"; + + private ArrayList mKeys; + private ArrayList mValues; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + onInit(); + } else { + onRestore(savedInstanceState); + } + } + + private void handlerArgs(Bundle args) { + if (args == null) { + return; + } + GalleryDetail gd = args.getParcelable(KEY_GALLERY_DETAIL); + if (gd == null) { + return; + } + + Resources resources = getResources(); + mKeys.add(resources.getString(R.string.header_key)); + mValues.add(resources.getString(R.string.header_value)); + mKeys.add(resources.getString(R.string.key_gid)); + mValues.add(Integer.toString(gd.gid)); + mKeys.add(resources.getString(R.string.key_token)); + mValues.add(gd.token); + mKeys.add(resources.getString(R.string.key_url)); + mValues.add(EhUrl.getGalleryDetailUrl(gd.gid, gd.token)); + mKeys.add(resources.getString(R.string.key_title)); + mValues.add(gd.title); + mKeys.add(resources.getString(R.string.key_title_jpn)); + mValues.add(gd.titleJpn); + mKeys.add(resources.getString(R.string.key_thumb)); + mValues.add(gd.thumb); + mKeys.add(resources.getString(R.string.key_category)); + mValues.add(EhUtils.getCategory(gd.category)); + mKeys.add(resources.getString(R.string.key_uploader)); + mValues.add(gd.uploader); + mKeys.add(resources.getString(R.string.key_posted)); + mValues.add(gd.posted); + mKeys.add(resources.getString(R.string.key_parent)); + mValues.add(gd.parent); + mKeys.add(resources.getString(R.string.key_visible)); + mValues.add(gd.visible); + mKeys.add(resources.getString(R.string.key_language)); + mValues.add(gd.language); + mKeys.add(resources.getString(R.string.key_pages)); + mValues.add(Integer.toString(gd.pages)); + mKeys.add(resources.getString(R.string.key_size)); + mValues.add(gd.size); + mKeys.add(resources.getString(R.string.key_resize)); + mValues.add(gd.resize); + mKeys.add(resources.getString(R.string.key_favourites)); + mValues.add(Integer.toString(gd.favoredTimes)); + mKeys.add(resources.getString(R.string.key_favourited)); + mValues.add(Boolean.toString(gd.isFavored)); + mKeys.add(resources.getString(R.string.key_rates)); + mValues.add(Integer.toString(gd.ratedTimes)); + mKeys.add(resources.getString(R.string.key_rating)); + mValues.add(Float.toString(gd.rating)); + mKeys.add(resources.getString(R.string.key_torrents)); + mValues.add(Integer.toString(gd.torrentCount)); + mKeys.add(resources.getString(R.string.key_torrent_url)); + mValues.add(gd.torrentUrl); + } + + protected void onInit() { + mKeys = new ArrayList<>(); + mValues = new ArrayList<>(); + handlerArgs(getArguments()); + } + + protected void onRestore(@NonNull Bundle savedInstanceState) { + mKeys = savedInstanceState.getStringArrayList(KEY_KEYS); + mValues = savedInstanceState.getStringArrayList(KEY_VALUES); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putStringArrayList(KEY_KEYS, mKeys); + outState.putStringArrayList(KEY_VALUES, mValues); + } + + @Nullable + @Override + public View onCreateView2(LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.scene_gallery_info, container, false); + + EasyRecyclerView recyclerView = (EasyRecyclerView) view.findViewById(R.id.recycler_view); + InfoAdapter adapter = new InfoAdapter(); + recyclerView.setAdapter(adapter); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); + LinearDividerItemDecoration decoration = new LinearDividerItemDecoration( + LinearDividerItemDecoration.VERTICAL, getResources().getColor(R.color.divider), + LayoutUtils.dp2pix(getContext(), 1)); + decoration.setPadding(getResources().getDimensionPixelOffset(R.dimen.keyline_margin)); + recyclerView.addItemDecoration(decoration); + recyclerView.setSelector(RippleSalon.generateRippleDrawable(false)); + recyclerView.setClipToPadding(false); + recyclerView.setHasFixedSize(true); + recyclerView.setOnItemClickListener(this); + return view; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setTitle(R.string.gallery_info); + setNavigationIcon(VectorDrawable.create(getContext(), R.drawable.ic_arrow_left)); + } + + @Override + public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) { + if (position != 0) { + ClipboardManager cmb = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + cmb.setPrimaryClip(ClipData.newPlainText(null, mValues.get(position))); + Toast.makeText(getContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); + return true; + } else { + return false; + } + } + + @Override + public void onNavigationClick() { + finish(); + } + + private static class InfoHolder extends RecyclerView.ViewHolder { + + private TextView key; + private TextView value; + + public InfoHolder(View itemView) { + super(itemView); + + key = (TextView) itemView.findViewById(R.id.key); + value = (TextView) itemView.findViewById(R.id.value); + } + } + + private class InfoAdapter extends RecyclerView.Adapter { + + private static final int TYPE_HEADER = 0; + private static final int TYPE_DATA = 1; + + @Override + public int getItemViewType(int position) { + if (position == 0) { + return TYPE_HEADER; + } else { + return TYPE_DATA; + } + } + + @Override + public InfoHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new InfoHolder(getActivity().getLayoutInflater().inflate(viewType == TYPE_HEADER ? + R.layout.item_gallery_info_header : R.layout.item_gallery_info_data, parent, false)); + } + + @Override + public void onBindViewHolder(InfoHolder holder, int position) { + holder.key.setText(mKeys.get(position)); + holder.value.setText(mValues.get(position)); + holder.itemView.setEnabled(position != 0); + } + + @Override + public int getItemCount() { + return mKeys == null || mValues == null ? 0 : Math.min(mKeys.size(), mValues.size()); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java index 038352e8e..2ea5401d3 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java @@ -45,14 +45,14 @@ import android.view.WindowManager; import android.widget.TextView; import android.widget.Toast; +import com.hippo.anani.AnimationUtils; import com.hippo.anani.SimpleAnimatorListener; import com.hippo.drawable.AddDeleteDrawable; import com.hippo.drawable.DrawerArrowDrawable; import com.hippo.easyrecyclerview.EasyRecyclerView; import com.hippo.easyrecyclerview.FastScroller; -import com.hippo.easyrecyclerview.MarginItemDecoration; import com.hippo.ehviewer.EhApplication; -import com.hippo.ehviewer.EhCacheKeyFactory; +import com.hippo.ehviewer.client.EhCacheKeyFactory; import com.hippo.ehviewer.R; import com.hippo.ehviewer.client.EhClient; import com.hippo.ehviewer.client.EhRequest; @@ -94,7 +94,7 @@ public final class GalleryListScene extends BaseScene public final static String KEY_ACTION = "action"; public final static String ACTION_HOMEPAGE = "action_homepage"; public final static String ACTION_WHATS_HOT = "action_whats_hot"; - public final static String ACTION_SEARCH = "action_search"; + public final static String ACTION_LIST_URL_BUILDER = "action_list_url_builder"; public final static String KEY_LIST_URL_BUILDER = "list_url_builder"; public final static String KEY_HAS_FIRST_REFRESH = "has_first_refresh"; @@ -157,9 +157,11 @@ public final class GalleryListScene extends BaseScene mUrlBuilder.reset(); } else if (ACTION_WHATS_HOT.equals(action)) { mUrlBuilder.setMode(ListUrlBuilder.MODE_WHATS_HOT); - } else if (ACTION_SEARCH.equals(action)) { - mUrlBuilder.reset(); - // TODO + } else if (ACTION_LIST_URL_BUILDER.equals(action)) { + ListUrlBuilder builder = args.getParcelable(KEY_LIST_URL_BUILDER); + if (builder != null) { + mUrlBuilder.set(builder); + } } } @@ -275,8 +277,6 @@ public final class GalleryListScene extends BaseScene mRecyclerView.hasFixedSize(); mRecyclerView.setClipToPadding(false); mRecyclerView.setOnItemClickListener(this); - mRecyclerView.addItemDecoration(new MarginItemDecoration( - getContext().getResources().getDimensionPixelOffset(R.dimen.list_item_margin))); int paddingH = resources.getDimensionPixelOffset(R.dimen.list_content_margin_h); int paddingV = resources.getDimensionPixelOffset(R.dimen.list_content_margin_v); mRecyclerView.setPadding(paddingV, paddingH, paddingV, paddingH); @@ -434,7 +434,8 @@ public final class GalleryListScene extends BaseScene mFab.setVisibility(View.VISIBLE); mFab.setScaleX(0.0f); mFab.setScaleY(0.0f); - mFab.animate().scaleX(1.0f).scaleY(1.0f).setListener(null).setDuration(ANIMATE_TIME).start(); + mFab.animate().scaleX(1.0f).scaleY(1.0f).setListener(null).setDuration(ANIMATE_TIME) + .setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start(); } private void hideFab() { @@ -446,7 +447,8 @@ public final class GalleryListScene extends BaseScene } }; } - mFab.animate().scaleX(0.0f).scaleY(0.0f).setListener(mFabAnimatorListener).setDuration(ANIMATE_TIME).start(); + mFab.animate().scaleX(0.0f).scaleY(0.0f).setListener(mFabAnimatorListener) + .setDuration(ANIMATE_TIME).setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).start(); } private void setState(@State int state) { @@ -584,6 +586,7 @@ public final class GalleryListScene extends BaseScene mSearchLayout.formatListUrlBuilder(mUrlBuilder, query); } else { mUrlBuilder.reset(); + mUrlBuilder.setKeyword(query); } onUpdateUrlBuilder(); mHelper.refresh(); @@ -649,14 +652,14 @@ public final class GalleryListScene extends BaseScene @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState){ - if (newState == RecyclerView.SCROLL_STATE_IDLE && isVaildView(recyclerView)) { + if (newState == RecyclerView.SCROLL_STATE_IDLE && isValidView(recyclerView)) { returnSearchBarPosition(); } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - if (isVaildView(recyclerView)) { + if (isValidView(recyclerView)) { int oldBottom = mSearchBar.getBottom(); OffsetLayout.LayoutParams sbLayoutParams = (OffsetLayout.LayoutParams) mSearchBar.getLayoutParams(); @@ -673,12 +676,12 @@ public final class GalleryListScene extends BaseScene } } - private boolean isVaildView(RecyclerView view) { + private boolean isValidView(RecyclerView view) { return (mState == STATE_NORMAL && view == mRecyclerView) || (mState == STATE_SEARCH && view == mSearchLayout); } - private RecyclerView getVaildRecyclerView() { + private RecyclerView getValidRecyclerView() { if (mState == STATE_NORMAL || mState == STATE_SIMPLE_SEARCH) { return mRecyclerView; } else { @@ -700,7 +703,7 @@ public final class GalleryListScene extends BaseScene if (mState == STATE_SIMPLE_SEARCH || mState == STATE_SEARCH_SHOW_LIST) { show = true; } else { - RecyclerView recyclerView = getVaildRecyclerView(); + RecyclerView recyclerView = getValidRecyclerView(); if (!recyclerView.isShown()) { show = true; } else if (recyclerView.computeVerticalScrollOffset() < getSearchBarOriginalBottom()){ @@ -812,6 +815,13 @@ public final class GalleryListScene extends BaseScene } } + public static void startScene(SceneFragment scene, ListUrlBuilder lub) { + Bundle args = new Bundle(); + args.putString(KEY_ACTION, ACTION_LIST_URL_BUILDER); + args.putParcelable(KEY_LIST_URL_BUILDER, lub); + scene.startScene(GalleryListScene.class, args); + } + private class GalleryListHolder extends RecyclerView.ViewHolder { private LoadImageView thumb; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/ToolbarScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/ToolbarScene.java new file mode 100644 index 000000000..99c869cb1 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/ToolbarScene.java @@ -0,0 +1,129 @@ +/* + * 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.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.hippo.ehviewer.R; + +public abstract class ToolbarScene extends BaseScene { + + private Toolbar mToolbar; + + @Nullable + public View onCreateView2(LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return null; + } + + @Nullable + @Override + public final View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.scene_toolbar, container, false); + Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar); + FrameLayout contentPanel = (FrameLayout) view.findViewById(R.id.content_panel); + + View contentView = onCreateView2(inflater, contentPanel, savedInstanceState); + if (contentView == null) { + return null; + } else { + mToolbar = toolbar; + contentPanel.addView(contentView, 0); + return view; + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + mToolbar = null; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (mToolbar != null) { + int menuResId = getMenuResId(); + if (menuResId != 0) { + mToolbar.inflateMenu(menuResId); + mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return ToolbarScene.this.onMenuItemClick(item); + } + }); + } + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onNavigationClick(); + } + }); + onMenuCreated(mToolbar.getMenu()); + } + } + + public int getMenuResId() { + return 0; + } + + public void onMenuCreated(Menu menu) { + } + + public boolean onMenuItemClick(MenuItem item) { + return false; + } + + public void onNavigationClick() { + } + + public void setNavigationIcon(@DrawableRes int resId) { + if (mToolbar != null) { + mToolbar.setNavigationIcon(resId); + } + } + + public void setNavigationIcon(@Nullable Drawable icon) { + if (mToolbar != null) { + mToolbar.setNavigationIcon(icon); + } + } + + public void setTitle(@StringRes int resId) { + if (mToolbar != null) { + mToolbar.setTitle(resId); + } + } + + public void setTitle(CharSequence title) { + if (mToolbar != null) { + mToolbar.setTitle(title); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SearchBar.java b/app/src/main/java/com/hippo/ehviewer/widget/SearchBar.java index 784fcdd22..538f29bc7 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/SearchBar.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/SearchBar.java @@ -143,7 +143,7 @@ public class SearchBar extends CardView implements View.OnClickListener, String suggestion = mSuggestionList.get(MathUtils.clamp( position - mList.getHeaderViewsCount(), 0, mSuggestionList.size() - 1)); mEditText.setText(suggestion); - mEditText.setSelection(suggestion.length()); + mEditText.setSelection(mEditText.getText().length()); } }); } diff --git a/app/src/main/java/com/hippo/text/URLImageGetter.java b/app/src/main/java/com/hippo/text/URLImageGetter.java new file mode 100644 index 000000000..835ab1b69 --- /dev/null +++ b/app/src/main/java/com/hippo/text/URLImageGetter.java @@ -0,0 +1,43 @@ +/* + * 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.text; + +import android.graphics.drawable.Drawable; +import android.widget.TextView; + +import com.hippo.conaco.Conaco; +import com.hippo.conaco.ConacoTask; +import com.hippo.drawable.ImageWrapper; +import com.hippo.drawable.UnikeryDrawable; + +public class URLImageGetter implements Html.ImageGetter { + + private TextView mTextView; + private Conaco mConaco; + + public URLImageGetter(TextView textView, Conaco conaco) { + mTextView = textView; + mConaco = conaco; + } + + @Override + public Drawable getDrawable(String source) { + UnikeryDrawable drawable = new UnikeryDrawable(mTextView); + mConaco.load(new ConacoTask.Builder().setUnikery(drawable).setUrl(source).setKey(source)); + return drawable; + } +} diff --git a/app/src/main/java/com/hippo/utils/ActivityHelper.java b/app/src/main/java/com/hippo/utils/ActivityHelper.java new file mode 100644 index 000000000..2763cdcda --- /dev/null +++ b/app/src/main/java/com/hippo/utils/ActivityHelper.java @@ -0,0 +1,65 @@ +/* + * 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.utils; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.widget.Toast; + +import com.hippo.ehviewer.R; + +public class ActivityHelper { + + public static boolean sendEmail(@NonNull Activity from, @NonNull String address, + @Nullable String subject, @Nullable String text) { + Intent i = new Intent(Intent.ACTION_SENDTO); + i.setData(Uri.parse("mailto:" + address)); + if (subject != null) { + i.putExtra(Intent.EXTRA_SUBJECT, subject); + } + if (text != null) { + i.putExtra(Intent.EXTRA_TEXT, text); + } + + try { + from.startActivity(i); + return true; + } catch (ActivityNotFoundException e) { + Toast.makeText(from, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show(); + return false; + } + } + + public static boolean share(@NonNull Activity from, String text) { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, text); + sendIntent.setType("text/plain"); + + try { + from.startActivity(sendIntent); + return true; + } catch (ActivityNotFoundException e) { + Toast.makeText(from, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show(); + return false; + } + } +} diff --git a/app/src/main/java/com/hippo/utils/CustomTabsHelper.java b/app/src/main/java/com/hippo/utils/CustomTabsHelper.java new file mode 100644 index 000000000..4186ebf6a --- /dev/null +++ b/app/src/main/java/com/hippo/utils/CustomTabsHelper.java @@ -0,0 +1,167 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// 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.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for Custom Tabs. + */ +public class CustomTabsHelper { + private static final String TAG = "CustomTabsHelper"; + static final String STABLE_PACKAGE = "com.android.chrome"; + static final String BETA_PACKAGE = "com.chrome.beta"; + static final String DEV_PACKAGE = "com.chrome.dev"; + static final String LOCAL_PACKAGE = "com.google.android.apps.chrome"; + private static final String ACTION_CUSTOM_TABS_CONNECTION = + "android.support.customtabs.action.CustomTabsService"; + private static final String UNINITIALIZED = "UNINITIALIZED"; + + private static String sPackageNameToUse = UNINITIALIZED; + + private CustomTabsHelper() {} + + /** + * The browser of smartisan is terrible + */ + public static String getPackageNameToUseFixed(Context context) { + if (!UNINITIALIZED.equals(sPackageNameToUse)) return sPackageNameToUse; + + PackageManager pm = context.getPackageManager(); + + // Get all apps that can handle VIEW intents. + Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")); + List resolvedActivityList = pm.queryIntentActivities(activityIntent, 0); + List packagesSupportingCustomTabs = new ArrayList<>(); + for (ResolveInfo info : resolvedActivityList) { + Intent serviceIntent = new Intent(); + serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION); + serviceIntent.setPackage(info.activityInfo.packageName); + if (pm.resolveService(serviceIntent, 0) != null) { + packagesSupportingCustomTabs.add(info.activityInfo.packageName); + } + } + + // Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents + // and service calls. + if (packagesSupportingCustomTabs.isEmpty()) { + sPackageNameToUse = null; + } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) { + sPackageNameToUse = STABLE_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) { + sPackageNameToUse = BETA_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) { + sPackageNameToUse = DEV_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) { + sPackageNameToUse = LOCAL_PACKAGE; + } else { + sPackageNameToUse = null; + } + return sPackageNameToUse; + } + + /** + * Goes through all apps that handle VIEW intents and have a warmup service. Picks + * the one chosen by the user if there is one, otherwise makes a best effort to return a + * valid package name. + * + * This is not threadsafe. + * + * @param context {@link Context} to use for accessing {@link PackageManager}. + * @return The package name recommended to use for connecting to custom tabs related components. + */ + public static String getPackageNameToUse(Context context) { + if (sPackageNameToUse != null) return sPackageNameToUse; + + PackageManager pm = context.getPackageManager(); + // Get default VIEW intent handler. + Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")); + ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0); + String defaultViewHandlerPackageName = null; + if (defaultViewHandlerInfo != null) { + defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName; + } + + // Get all apps that can handle VIEW intents. + List resolvedActivityList = pm.queryIntentActivities(activityIntent, 0); + List packagesSupportingCustomTabs = new ArrayList<>(); + for (ResolveInfo info : resolvedActivityList) { + Intent serviceIntent = new Intent(); + serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION); + serviceIntent.setPackage(info.activityInfo.packageName); + if (pm.resolveService(serviceIntent, 0) != null) { + packagesSupportingCustomTabs.add(info.activityInfo.packageName); + } + } + + // Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents + // and service calls. + if (packagesSupportingCustomTabs.isEmpty()) { + sPackageNameToUse = null; + } else if (packagesSupportingCustomTabs.size() == 1) { + sPackageNameToUse = packagesSupportingCustomTabs.get(0); + } else if (!TextUtils.isEmpty(defaultViewHandlerPackageName) + && !hasSpecializedHandlerIntents(context, activityIntent) + && packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) { + sPackageNameToUse = defaultViewHandlerPackageName; + } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) { + sPackageNameToUse = STABLE_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) { + sPackageNameToUse = BETA_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) { + sPackageNameToUse = DEV_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) { + sPackageNameToUse = LOCAL_PACKAGE; + } + return sPackageNameToUse; + } + + /** + * Used to check whether there is a specialized handler for a given intent. + * @param intent The intent to check with. + * @return Whether there is a specialized handler for the given intent. + */ + private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) { + try { + PackageManager pm = context.getPackageManager(); + List handlers = pm.queryIntentActivities( + intent, + PackageManager.GET_RESOLVED_FILTER); + if (handlers == null || handlers.size() == 0) { + return false; + } + for (ResolveInfo resolveInfo : handlers) { + IntentFilter filter = resolveInfo.filter; + if (filter == null) continue; + if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue; + if (resolveInfo.activityInfo == null) continue; + return true; + } + } catch (RuntimeException e) { + Log.e(TAG, "Runtime exception while getting specialized handlers"); + } + return false; + } +} diff --git a/app/src/main/java/com/hippo/utils/OpenUrlHelper.java b/app/src/main/java/com/hippo/utils/OpenUrlHelper.java new file mode 100644 index 000000000..653dde214 --- /dev/null +++ b/app/src/main/java/com/hippo/utils/OpenUrlHelper.java @@ -0,0 +1,97 @@ +/* + * 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.utils; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.support.customtabs.CustomTabsIntent; +import android.text.TextUtils; + +import com.hippo.ehviewer.R; + +public final class OpenUrlHelper { + + private OpenUrlHelper() { + } + + public static void openUrl(Context context, String url, boolean checkNMB) { + if (TextUtils.isEmpty(url)) { + return; + } + + Intent intent; + Uri uri = Uri.parse(url); + + /* + // Is nmb url + if (checkNMB) { + NMBUriParser.PostResult result = NMBUriParser.parsePostUri(uri); + if (result.site != null && result.id != null) { + intent = new Intent(context, PostActivity.class); + intent.setAction(PostActivity.ACTION_SITE_ID); + intent.putExtra(PostActivity.KEY_SITE, result.site.getId()); + intent.putExtra(PostActivity.KEY_ID, result.id); + if (!(context instanceof Activity)) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + context.startActivity(intent); + return; + } + } + */ + + // CustomTabs + if (context instanceof Activity) { + String packageName = CustomTabsHelper.getPackageNameToUseFixed(context); + if (packageName != null) { + new CustomTabsIntent.Builder() + .setToolbarColor(context.getResources().getColor(R.color.colorPrimary)) + .setShowTitle(true) + .build() + .launchUrl((Activity) context, uri); + return; + } + } + + // Intent.ACTION_VIEW + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(uri); + PackageManager pm = context.getPackageManager(); + ResolveInfo ri = pm.resolveActivity(intent, 0); + if (ri != null) { + if (!(context instanceof Activity)) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + context.startActivity(intent); + return; + } + + /* + // Use WebViewActivity + intent = new Intent(context, WebViewActivity.class); + intent.putExtra(WebViewActivity.KEY_URL, url); + if (!(context instanceof Activity)) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + context.startActivity(intent); + */ + } +} diff --git a/app/src/main/java/com/hippo/utils/TextUrl.java b/app/src/main/java/com/hippo/utils/TextUrl.java new file mode 100644 index 000000000..20bd9c854 --- /dev/null +++ b/app/src/main/java/com/hippo/utils/TextUrl.java @@ -0,0 +1,59 @@ +/* + * 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.utils; + +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.URLSpan; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class TextUrl { + + private static final Pattern URL_PATTERN = Pattern.compile("(http|https)://[a-z0-9A-Z%-]+(\\.[a-z0-9A-Z%-]+)+(:\\d{1,5})?(/[a-zA-Z0-9-_~:#@!&',;=%/\\*\\.\\?\\+\\$\\[\\]\\(\\)]+)?/?"); + + public static CharSequence handleTextUrl(CharSequence content) { + Matcher m = URL_PATTERN.matcher(content); + + Spannable spannable = null; + while (m.find()) { + // Ensure spannable + if (spannable == null) { + if (content instanceof Spannable) { + spannable = (Spannable) content; + } else { + spannable = new SpannableString(content); + } + } + + int start = m.start(); + int end = m.end(); + + URLSpan[] links = spannable.getSpans(start, end, URLSpan.class); + if (links.length > 0) { + // There has been URLSpan already, leave it alone + continue; + } + + URLSpan urlSpan = new URLSpan(m.group(0)); + spannable.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + return spannable == null ? content : spannable; + } +} diff --git a/app/src/main/java/com/hippo/widget/SimpleGridLayout.java b/app/src/main/java/com/hippo/widget/SimpleGridLayout.java new file mode 100644 index 000000000..849822448 --- /dev/null +++ b/app/src/main/java/com/hippo/widget/SimpleGridLayout.java @@ -0,0 +1,174 @@ +/* + * 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.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.hippo.ehviewer.R; +import com.hippo.yorozuya.MathUtils; +import com.hippo.yorozuya.ViewUtils; + +/** + * not scrollable + * + * @author Hippo + * + */ +public class SimpleGridLayout extends ViewGroup { + + private static final int DEFAULT_COLUMN_COUNT = 3; + + private int mColumnCount; + private int mItemMargin; + + private int[] mRowHeights; + private int mItemWidth; + + public SimpleGridLayout(Context context) { + super(context); + init(context, null); + } + + public SimpleGridLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public SimpleGridLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + public void init(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleGridLayout); + mColumnCount = a.getInteger(R.styleable.SimpleGridLayout_columnCount, DEFAULT_COLUMN_COUNT); + mItemMargin = a.getDimensionPixelOffset(R.styleable.SimpleGridLayout_itemMargin, 0); + a.recycle(); + } + + public void setItemMargin(int itemMargin) { + if (mItemMargin != itemMargin) { + mItemMargin = itemMargin; + requestLayout(); + } + } + + public void setColumnCount(int columnCount) { + if (columnCount <= 0) { + throw new IllegalStateException("Column count can't be " + columnCount); + } + + if (mColumnCount != columnCount) { + mColumnCount = columnCount; + requestLayout(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int maxRowCount = MathUtils.ceilDivide(getChildCount(), mColumnCount); + if (mRowHeights == null || mRowHeights.length != maxRowCount) { + mRowHeights = new int[maxRowCount]; + } + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int maxWidth = MeasureSpec.getSize(widthMeasureSpec); + int maxHeight = MeasureSpec.getSize(heightMeasureSpec); + + if (widthMode == MeasureSpec.UNSPECIFIED) { + maxWidth = 300; + } + if (heightMode == MeasureSpec.UNSPECIFIED) { + maxHeight = ViewUtils.MAX_SIZE; + } + + // Get item width MeasureSpec + mItemWidth = Math.max( + (maxWidth - getPaddingLeft() - getPaddingRight() - ((mColumnCount - 1) * mItemMargin)) / mColumnCount, 1); + int itemWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mItemWidth, MeasureSpec.EXACTLY); + int itemHeightMeasureSpec = MeasureSpec.UNSPECIFIED; + + int measuredWidth = maxWidth; + int measuredHeight = 0; + int rowHeight = 0; + int row = 0; + int count = getChildCount(); + for (int index = 0, indexInRow = 0; index < count; index++, indexInRow++) { + final View child = getChildAt(index); + if (child.getVisibility() == View.GONE) { + indexInRow--; + continue; + } + + child.measure(itemWidthMeasureSpec, itemHeightMeasureSpec); + + if (indexInRow == mColumnCount) { + // New row + indexInRow = 0; + rowHeight = 0; + row++; + } + + rowHeight = Math.max(rowHeight, child.getMeasuredHeight()); + + if (indexInRow == mColumnCount - 1 || index == count - 1) { + mRowHeights[row] = rowHeight; + measuredHeight += rowHeight + mItemMargin; + } + } + measuredHeight -= mItemMargin; + measuredHeight = Math.max(0, Math.min(measuredHeight + getPaddingTop() + getPaddingBottom(), maxHeight)); + + setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int itemWidth = mItemWidth; + int itemMargin = mItemMargin; + int paddingLeft = getPaddingLeft(); + int left = paddingLeft; + int top = getPaddingTop(); + int row = 0; + int count = getChildCount(); + for (int index = 0, indexInRow = 0; index < count; index++, indexInRow++) { + final View child = getChildAt(index); + if (child.getVisibility() == View.GONE) { + indexInRow--; + continue; + } + + if (indexInRow == mColumnCount) { + // New row + left = paddingLeft; + top += mRowHeights[row] + itemMargin; + + indexInRow = 0; + row++; + } + + child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); + + left += itemWidth + itemMargin; + } + } +} diff --git a/app/src/main/res/drawable/ic_arrow_left.xml b/app/src/main/res/drawable/ic_arrow_left.xml new file mode 100644 index 000000000..d681bb7dc --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_reply.xml b/app/src/main/res/drawable/ic_reply.xml new file mode 100644 index 000000000..c1461b40b --- /dev/null +++ b/app/src/main/res/drawable/ic_reply.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/layout/gallery_detail_comments.xml b/app/src/main/res/layout/gallery_detail_comments.xml index 960f9823b..ff22fac3c 100644 --- a/app/src/main/res/layout/gallery_detail_comments.xml +++ b/app/src/main/res/layout/gallery_detail_comments.xml @@ -20,15 +20,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:paddingLeft="@dimen/keyline_margin" - android:paddingRight="@dimen/keyline_margin"> + android:padding="@dimen/keyline_margin"> diff --git a/app/src/main/res/layout/gallery_detail_content.xml b/app/src/main/res/layout/gallery_detail_content.xml index 352fcd54b..a6f098364 100644 --- a/app/src/main/res/layout/gallery_detail_content.xml +++ b/app/src/main/res/layout/gallery_detail_content.xml @@ -60,13 +60,26 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_gallery_info_data.xml b/app/src/main/res/layout/item_gallery_info_data.xml new file mode 100644 index 000000000..e9a54a12d --- /dev/null +++ b/app/src/main/res/layout/item_gallery_info_data.xml @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/item_gallery_info_header.xml b/app/src/main/res/layout/item_gallery_info_header.xml new file mode 100644 index 000000000..ac68531c6 --- /dev/null +++ b/app/src/main/res/layout/item_gallery_info_header.xml @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/item_gallery_list.xml b/app/src/main/res/layout/item_gallery_list.xml index e8f94074b..6c8ccfc97 100644 --- a/app/src/main/res/layout/item_gallery_list.xml +++ b/app/src/main/res/layout/item_gallery_list.xml @@ -14,16 +14,20 @@ ~ limitations under the License. --> - + app:ms9_shadowTranslationZ="2dp" + app:ms9_affectsDisplayedPosition="false"> + android:layout_height="wrap_content" + android:layout_margin="4dp" + android:background="@android:color/white"> - + diff --git a/app/src/main/res/layout/item_preview.xml b/app/src/main/res/layout/item_preview.xml new file mode 100644 index 000000000..8984515dd --- /dev/null +++ b/app/src/main/res/layout/item_preview.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/scene_gallery_comments.xml b/app/src/main/res/layout/scene_gallery_comments.xml new file mode 100644 index 000000000..050e4803b --- /dev/null +++ b/app/src/main/res/layout/scene_gallery_comments.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/scene_gallery_detail.xml b/app/src/main/res/layout/scene_gallery_detail.xml index b6cf13f7e..cb0ab9c02 100644 --- a/app/src/main/res/layout/scene_gallery_detail.xml +++ b/app/src/main/res/layout/scene_gallery_detail.xml @@ -25,7 +25,8 @@ + android:layout_height="match_parent" + android:scrollbars="none"> diff --git a/app/src/main/res/layout/scene_gallery_info.xml b/app/src/main/res/layout/scene_gallery_info.xml new file mode 100644 index 000000000..57779664a --- /dev/null +++ b/app/src/main/res/layout/scene_gallery_info.xml @@ -0,0 +1,24 @@ + + + + diff --git a/app/src/main/res/layout/scene_toolbar.xml b/app/src/main/res/layout/scene_toolbar.xml new file mode 100644 index 000000000..a783aee32 --- /dev/null +++ b/app/src/main/res/layout/scene_toolbar.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_search_bar.xml b/app/src/main/res/layout/widget_search_bar.xml index f78ef57f2..cfe21becc 100644 --- a/app/src/main/res/layout/widget_search_bar.xml +++ b/app/src/main/res/layout/widget_search_bar.xml @@ -43,7 +43,9 @@ android:layout_marginRight="56dp" android:gravity="center_vertical" android:textSize="@dimen/text_large" - android:textColor="@color/secondary_text_light"/> + android:textColor="@color/secondary_text_light" + android:singleLine="true" + android:maxLines="1"/> diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 0509fa051..5440ed767 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -71,4 +71,9 @@ + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index bcdd03efd..b2a15b267 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -36,8 +36,8 @@ 120dp 80dp 2dp - 2dp - 2dp + 4dp + 4dp 4dp diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 12f4ca598..c3a5d9208 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -17,10 +17,7 @@ - - - - + diff --git a/app/src/main/res/values/pathdata.xml b/app/src/main/res/values/pathdata.xml index 765fba685..45e70004c 100644 --- a/app/src/main/res/values/pathdata.xml +++ b/app/src/main/res/values/pathdata.xml @@ -22,8 +22,9 @@ M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z - M12.1,18.55L12,18.65L11.89,18.55C7.14,14.24 4,11.39 4,8.5C4,6.5 5.5,5 7.5,5C9.04,5 10.54,6 11.07,7.36H12.93C13.46,6 14.96,5 16.5,5C18.5,5 20,6.5 20,8.5C20,11.39 16.86,14.24 12.1,18.55M16.5,3C14.76,3 13.09,3.81 12,5.08C10.91,3.81 9.24,3 7.5,3C4.42,3 2,5.41 2,8.5C2,12.27 5.4,15.36 10.55,20.03L12,21.35L13.45,20.03C18.6,15.36 22,12.27 22,8.5C22,5.41 19.58,3 16.5,3Z M12,12L12,12L12,12C12,12 12,12 12,12C12,12 12,12 12,12C12,12 12,12 12,12H12C12,12 12,12 12,12C12,12 12,12 12,12C12,12 12,12 12,12M16.5,3C14.76,3 13.09,3.81 12,5.08C10.91,3.81 9.24,3 7.5,3C4.42,3 2,5.41 2,8.5C2,12.27 5.4,15.36 10.55,20.03L12,21.35L13.45,20.03C18.6,15.36 22,12.27 22,8.5C22,5.41 19.58,3 16.5,3Z + 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 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f6daea64..5b4996af1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -79,6 +79,7 @@ Too many redirections Network error Weird + Can\'t find the application Username cannot be an empty string Password cannot be an empty string @@ -123,6 +124,7 @@ 5 stars + Can\'t find the gallery Read More information @@ -130,7 +132,7 @@ %d pages - %d \u2665 + \u2665 %d %1$s (%2$d) Favorite @@ -141,6 +143,9 @@ No comments No more comments More comments + No previews + No more previews + More previews Refresh Open in other app @@ -157,6 +162,36 @@ UNBEARABLE DISASTER + + Gallery Info + Copied to clipboard + Key + Value + Gid + Token + Url + Title + Jpn Title + Thumb + Category + Uploader + Posted + Parent + Visible + Language + Pages + Size + Resize + Favourites + Favourited + Rates + Rating + Torrents + Torrent Url + + + Gallery Comments + From the future Just now diff --git a/beerbelly b/beerbelly index 40be56ef2..a20b74d0a 160000 --- a/beerbelly +++ b/beerbelly @@ -1 +1 @@ -Subproject commit 40be56ef200386ec723155e9a4076ea32f869aa8 +Subproject commit a20b74d0aebd6065bd6b2a1c4b65e03105d676d1