Improve Gallery Detail

This commit is contained in:
seven332 committed 2016-02-03 14:15:40 +08:00
1 parent dd1daa96d2
commit 507c7e7879
42 files changed
+2174 -142

No files matched your search

@@ -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<ImageWrapper> {
private int mTaskId = Unikery.INVALID_ID;
private TextView mTextView;
private ValueHolder<ImageWrapper> 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<ImageWrapper> 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
}
}
@@ -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();
}
}
}
@@ -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<Object> mGlobalStuffMap = new SparseArray<>();
private EhCookieStore mEhCookieStore;
@@ -48,7 +51,7 @@ public class EhApplication extends SceneApplication {
private OkHttpClient mOkHttpClient;
private ImageWrapperHelper mImageWrapperHelper;
private Conaco<ImageWrapper> mConaco;
private LruMap<Integer, GalleryDetail> mGalleryDetailCache;
private LruCacheEx<Integer, GalleryDetail> 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<Integer, GalleryDetail> getGalleryDetailCache(@NonNull Context context) {
public static LruCacheEx<Integer, GalleryDetail> getGalleryDetailCache(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mGalleryDetailCache == null) {
application.mGalleryDetailCache = new LruMap<>(new Comparator<Integer>() {
// Max size 25, 3 min timeout
application.mGalleryDetailCache = new LruCacheEx<>(25, 3 * 60 * 1000, new Comparator<Integer>() {
@Override
public int compare(Integer lhs, Integer rhs) {
return lhs - rhs;
}
}, 3 * 60 * 1000); // 3 min timeout
});
}
return application.mGalleryDetailCache;
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
@@ -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<GalleryDetail> CREATOR = new Creator<GalleryDetail>() {
@@ -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<String> mImageUrlList = new ArrayList<>();
private ArrayList<String> 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<LargePreviewSet> CREATOR = new Parcelable.Creator<LargePreviewSet>() {
public LargePreviewSet createFromParcel(Parcel source) {
return new LargePreviewSet(source);
}
public LargePreviewSet[] newArray(int size) {
return new LargePreviewSet[size];
}
};
}
@@ -81,7 +81,7 @@ public class GalleryDetailParser {
public static final Pattern PAGES_PATTERN = Pattern.compile("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>");
public static final Pattern PREVIEW_PAGES_PATTERN = Pattern.compile("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?&gt;(?:</a>)?</td>");
private static final Pattern NORMAL_PREVIEW_PATTERN = Pattern.compile("<div[^<>]*class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*>");
private static final Pattern LARGE_PREVIEW_PATTERN = Pattern.compile("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img.+?src=\"(.+?)\"");
private static final Pattern LARGE_PREVIEW_PATTERN = Pattern.compile("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img alt=\"([\\d,]+)\".+?src=\"(.+?)\"");
static {
WEB_COMMENT_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -92,12 +92,7 @@ public class GalleryDetailParser {
private static final String PINING_STRING =
"<p>This gallery is pining for the fjords.</p>";
public static class Result {
public GalleryDetail galleryDetail;
public LargePreviewSet previewSet;
}
public static GalleryDetailParser.Result parse(String body) throws EhException {
public static GalleryDetail parse(String body) throws EhException {
if (body.contains(OFFENSIVE_STRING)) {
throw new OffensiveException();
}
@@ -117,16 +112,10 @@ public class GalleryDetailParser {
galleryDetail.tags = parseTagGroup(body);
galleryDetail.comments = parseComment(body);
galleryDetail.previewPages = parsePreviewPages(body);
LargePreviewSet previewSet = parsePreview(body);
Result result = new Result();
result.galleryDetail = galleryDetail;
result.previewSet = previewSet;
return result;
galleryDetail.previewSet = parsePreview(body);
return galleryDetail;
}
public static void parseDetail(String body, GalleryDetail gd) throws EhException {
Matcher m = DETAIL_PATTERN.matcher(body);
if (!m.find()) {
@@ -253,18 +242,23 @@ public class GalleryDetailParser {
}
public static LargePreviewSet parsePreview(String body) throws EhException {
return parseLargePreview(body);
/*
if (body.contains("<div class=\"gdtm\"")) {
throw new EhException("Not support normal preview now");
} else {
return parseLargePreview(body);
}
*/
}
private static LargePreviewSet parseLargePreview(String body) {
Matcher m = LARGE_PREVIEW_PATTERN.matcher(body);
LargePreviewSet largePreviewSet = new LargePreviewSet();
while (m.find()) {
largePreviewSet.addItem(m.group(2), m.group(1));
largePreviewSet.addItem(ParserUtils.parseInt(m.group(2)),
ParserUtils.trim(m.group(3)), ParserUtils.trim(m.group(1)));
}
return largePreviewSet;
}
@@ -0,0 +1,192 @@
/*
* 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.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.rippleold.RippleSalon;
import com.hippo.text.Html;
import com.hippo.text.URLImageGetter;
import com.hippo.utils.OpenUrlHelper;
import com.hippo.utils.ReadableTime;
import com.hippo.utils.TextUrl;
import com.hippo.vector.VectorDrawable;
import com.hippo.widget.LinkifyTextView;
import com.hippo.yorozuya.LayoutUtils;
public final class GalleryCommentsScene extends ToolbarScene
implements EasyRecyclerView.OnItemClickListener,
EasyRecyclerView.OnItemLongClickListener {
public static final String KEY_COMMENTS = "comments";
private GalleryComment[] mComments;
private CommentAdapter mAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
onInit();
} else {
onRestore(savedInstanceState);
}
}
private void handleArgs(Bundle args) {
if (args == null) {
return;
}
Parcelable[] parcelables = args.getParcelableArray(KEY_COMMENTS);
if (parcelables instanceof GalleryComment[]) {
mComments = (GalleryComment[]) parcelables;
}
}
private void onInit() {
handleArgs(getArguments());
}
private void onRestore(@NonNull Bundle savedInstanceState) {
Parcelable[] parcelables = savedInstanceState.getParcelableArray(KEY_COMMENTS);
if (parcelables instanceof GalleryComment[]) {
mComments = (GalleryComment[]) parcelables;
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArray(KEY_COMMENTS, mComments);
}
@Nullable
@Override
public View onCreateView2(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_gallery_comments, container, false);
EasyRecyclerView recyclerView = (EasyRecyclerView) view.findViewById(R.id.recycler_view);
FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fab);
mAdapter = new CommentAdapter();
recyclerView.setAdapter(mAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(),
LinearLayoutManager.VERTICAL, false));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL, getResources().getColor(R.color.divider),
LayoutUtils.dp2pix(getContext(), 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
recyclerView.setSelector(RippleSalon.generateRippleDrawable(false));
recyclerView.setHasFixedSize(true);
recyclerView.setOnItemClickListener(this);
recyclerView.setOnItemLongClickListener(this);
fab.setImageDrawable(VectorDrawable.create(getContext(), R.drawable.ic_reply));
return view;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setTitle(R.string.gallery_comments);
setNavigationIcon(VectorDrawable.create(getContext(), R.drawable.ic_arrow_left));
}
@Override
public void onNavigationClick() {
finish();
}
@Override
public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) {
RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
if (holder instanceof CommentHolder) {
CommentHolder commentHolder = (CommentHolder) holder;
ClickableSpan span = commentHolder.comment.getCurrentSpan();
commentHolder.comment.clearCurrentSpan();
if (span instanceof URLSpan) {
OpenUrlHelper.openUrl(getContext(), ((URLSpan) span).getURL(), true);
return true;
}
}
return false;
}
@Override
public boolean onItemLongClick(EasyRecyclerView parent, View view, int position, long id) {
return false;
}
private class CommentHolder extends RecyclerView.ViewHolder {
public TextView user;
public TextView time;
public LinkifyTextView comment;
public CommentHolder(View itemView) {
super(itemView);
user = (TextView) itemView.findViewById(R.id.user);
time = (TextView) itemView.findViewById(R.id.time);
comment = (LinkifyTextView) itemView.findViewById(R.id.comment);
}
}
private class CommentAdapter extends RecyclerView.Adapter<CommentHolder> {
@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;
}
}
}
@@ -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<GalleryDetailParser.Result> {
private static class GetGalleryDetailListener implements EhClient.Callback<GalleryDetail> {
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();
@@ -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<String> mKeys;
private ArrayList<String> 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<InfoHolder> {
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());
}
}
}
@@ -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;
@@ -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);
}
}
}
@@ -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());
}
});
}
@@ -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<ImageWrapper> mConaco;
public URLImageGetter(TextView textView, Conaco<ImageWrapper> conaco) {
mTextView = textView;
mConaco = conaco;
}
@Override
public Drawable getDrawable(String source) {
UnikeryDrawable drawable = new UnikeryDrawable(mTextView);
mConaco.load(new ConacoTask.Builder<ImageWrapper>().setUnikery(drawable).setUrl(source).setKey(source));
return drawable;
}
}
@@ -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;
}
}
}
@@ -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<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
List<String> 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 <strong>not</strong> 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<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
List<String> 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<ResolveInfo> 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;
}
}
@@ -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);
*/
}
}
@@ -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;
}
}
@@ -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;
}
}
}