diff --git a/app/src/main/java/com/hippo/animation/DurationInterpolator.java b/app/src/main/java/com/hippo/animation/DurationInterpolator.java new file mode 100644 index 000000000..36c00069e --- /dev/null +++ b/app/src/main/java/com/hippo/animation/DurationInterpolator.java @@ -0,0 +1,37 @@ +/* + * 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.animation; + +import android.animation.TimeInterpolator; + +public class DurationInterpolator implements TimeInterpolator { + + private float mDuration; + + public DurationInterpolator(float duration) { + mDuration = duration; + } + + @Override + public float getInterpolation(float input) { + if (input > mDuration) { + return (input - mDuration) / (1 - mDuration); + } else { + return 0f; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/EhImageKeyFactory.java b/app/src/main/java/com/hippo/ehviewer/EhImageKeyFactory.java index 665e41f08..3dc6c2188 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhImageKeyFactory.java +++ b/app/src/main/java/com/hippo/ehviewer/EhImageKeyFactory.java @@ -5,4 +5,18 @@ public class EhImageKeyFactory { 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; + } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java index 9f8bb9f46..5c44b7889 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -19,8 +19,9 @@ package com.hippo.ehviewer.client; import android.os.AsyncTask; import android.os.Process; -import com.hippo.ehviewer.data.GalleryDetail; -import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.client.data.GalleryDetail; +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.ehviewer.client.data.PreviewSet; import com.hippo.ehviewer.network.EhOkHttpClient; import com.hippo.network.ResponseCodeException; import com.hippo.util.PriorityThreadFactory; @@ -426,6 +427,10 @@ public final class EhClient { gd.tags = parser.tags; gd.comments = parser.comments; gd.previewPageCount = parser.previewPageCount; + gd.previewSetArray = new PreviewSet[gd.previewPageCount]; + gd.previewSetArray[0] = parser.previewSet; + gd.previewSetArray[0].setStartIndex(0); + gd.previewSetArray[0].setGid(gd.gid); return gd; } diff --git a/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java b/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java index 9220f68b0..b37b7fb7c 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java +++ b/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java @@ -16,8 +16,11 @@ package com.hippo.ehviewer.client; -import com.hippo.ehviewer.data.Comment; -import com.hippo.ehviewer.data.TagGroup; +import com.hippo.ehviewer.client.data.Comment; +import com.hippo.ehviewer.client.data.LargePreviewSet; +import com.hippo.ehviewer.client.data.NormalPreviewSet; +import com.hippo.ehviewer.client.data.PreviewSet; +import com.hippo.ehviewer.client.data.TagGroup; import com.hippo.ehviewer.util.EhUtils; import com.hippo.util.AssertUtils; import com.hippo.util.Utils; @@ -52,6 +55,7 @@ public class GalleryDetailParser { public static final int REQUEST_DETAIL = 0x1; public static final int REQUEST_TAG = 0x2; public static final int REQUEST_PREVIEW_INFO = 0x4; + public static final int REQUEST_PREVIEW = 0x8; public static final int REQUEST_COMMENT = 0x10; private static final CommentSort COMMENT_SORTER = new CommentSort(); @@ -84,6 +88,8 @@ public class GalleryDetailParser { public int previewPageCount; + public PreviewSet previewSet; + private String sortOut(String str) { // Avoid null if (str == null) { @@ -104,6 +110,36 @@ public class GalleryDetailParser { } } + private void parseNormalPreview(String body) { + Pattern p = Pattern.compile("]*class=\"gdtm\"[^<>]*>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*>]*href=\"(.+?)\"[^<>]*>"); + Matcher m = p.matcher(body); + NormalPreviewSet normalPreviewSet = new NormalPreviewSet(); + while (m.find()) { + normalPreviewSet.addItem(m.group(3), Integer.parseInt(m.group(4)), 0, + Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)), + m.group(5)); + } + previewSet = normalPreviewSet; + } + + private void parseLargePreview(String body) { + Pattern p = Pattern.compile("
Posted on ([^<>]+) by:   ]+>([^<>]+).+?
]*>(.+?)
mItemList = new ArrayList<>(); + + @Override + public int size() { + return mItemList.size(); + } + + public void addItem(String imageUrl, String pageUrl) { + mItemList.add(new Item(imageUrl, pageUrl)); + } + + @Override + public void bindView(SimpleGridLayout simpleGridLayout, LayoutInflater inflater, Conaco conaco) { + ensureViewGroup(simpleGridLayout, inflater); + + int size = mItemList.size(); + for (int i = 0; i < size; i++) { + final Item item = mItemList.get(i); + final int index = i + getStartIndex(); + final ViewGroup viewGroup = (ViewGroup) simpleGridLayout.getChildAt(i); + conaco.load((LoadImageView) viewGroup.getChildAt(0), + EhImageKeyFactory.getLargePreviewKey(getGid(), index), item.imageUrl); + ((TextView) viewGroup.getChildAt(1)).setText(Integer.toString(index + 1)); + } + } + + @Override + public void cancelLoadTask(SimpleGridLayout simpleGridLayout, Conaco conaco) { + int count = simpleGridLayout.getChildCount(); + for (int i = 0; i < count; i++) { + final ViewGroup viewGroup = (ViewGroup) simpleGridLayout.getChildAt(i); + conaco.cancel((LoadImageView) viewGroup.getChildAt(0)); + } + } + + private class Item { + + public String imageUrl; + public String pageUrl; + + public Item(String imageUrl, String pageUrl) { + this.imageUrl = imageUrl; + this.pageUrl = pageUrl; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/ListUrlBuilder.java b/app/src/main/java/com/hippo/ehviewer/client/data/ListUrlBuilder.java similarity index 99% rename from app/src/main/java/com/hippo/ehviewer/data/ListUrlBuilder.java rename to app/src/main/java/com/hippo/ehviewer/client/data/ListUrlBuilder.java index b182020a6..8c2b60b05 100644 --- a/app/src/main/java/com/hippo/ehviewer/data/ListUrlBuilder.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/ListUrlBuilder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.data; +package com.hippo.ehviewer.client.data; import com.hippo.ehviewer.client.EhClient; import com.hippo.ehviewer.util.EhUtils; diff --git a/app/src/main/java/com/hippo/ehviewer/data/LofiGalleryInfo.java b/app/src/main/java/com/hippo/ehviewer/client/data/LofiGalleryInfo.java similarity index 98% rename from app/src/main/java/com/hippo/ehviewer/data/LofiGalleryInfo.java rename to app/src/main/java/com/hippo/ehviewer/client/data/LofiGalleryInfo.java index f490d280c..80c79da86 100644 --- a/app/src/main/java/com/hippo/ehviewer/data/LofiGalleryInfo.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/LofiGalleryInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.data; +package com.hippo.ehviewer.client.data; import android.os.Parcel; diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/NormalPreviewSet.java b/app/src/main/java/com/hippo/ehviewer/client/data/NormalPreviewSet.java new file mode 100644 index 000000000..c04f8d713 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/data/NormalPreviewSet.java @@ -0,0 +1,230 @@ +/* + * 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.ehviewer.client.data; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.hippo.conaco.BitmapHolder; +import com.hippo.conaco.Conaco; +import com.hippo.conaco.Unikery; +import com.hippo.ehviewer.EhImageKeyFactory; +import com.hippo.util.Log; +import com.hippo.widget.SimpleGridLayout; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class NormalPreviewSet extends PreviewSet { + + private List mRowArray = new ArrayList<>(); + + private Set mLoadImageTasks = new HashSet<>(); + + @Override + public int size() { + int size = 0; + for (Row row : mRowArray) { + size += row.itemArray.size(); + } + return size; + } + + public void addItem(String imageUrl, int xOffset, int yOffset, int width, + int height, String url) { + Row row; + int size = mRowArray.size(); + if (size == 0 || ! (row = mRowArray.get(size - 1)).imageUrl.equals(imageUrl)) { + row = new Row(imageUrl); + mRowArray.add(row); + } + row.addItem(xOffset, yOffset, width, height, url); + } + + @Override + public void bindView(SimpleGridLayout simpleGridLayout, + LayoutInflater inflater, Conaco conaco) { + ensureViewGroup(simpleGridLayout, inflater); + + int i = 0; + for (Row row : mRowArray) { + int rowSize = row.itemArray.size(); + ImageView[] imageViews = new ImageView[rowSize]; + for (int j = 0; j < rowSize; j++) { + ViewGroup viewGroup = (ViewGroup) simpleGridLayout.getChildAt(i); + imageViews[j] = (ImageView) viewGroup.getChildAt(0); + ((TextView) viewGroup.getChildAt(1)).setText(Integer.toString(i + getStartIndex() + 1)); + i++; + } + + LoadImageTask loadImageTask = new LoadImageTask(row, imageViews); + mLoadImageTasks.add(loadImageTask); + + String key; + if (row.imageIndex != -1) { + key = EhImageKeyFactory.getNormalPreviewKey(getGid(), row.imageIndex); + } else { + key = row.imageUrl; + } + conaco.load(loadImageTask, key, row.imageUrl); + } + } + + @Override + public void cancelLoadTask(SimpleGridLayout simpleGridLayout, Conaco conaco) { + for (LoadImageTask loadImageTask : mLoadImageTasks) { + conaco.cancel(loadImageTask); + } + mLoadImageTasks.clear(); + } + + class LoadImageTask implements Unikery { + + private int mTaskId; + private Row mRow; + private ImageView[] mImageViews; + + public LoadImageTask(Row row, ImageView[] imageViews) { + mRow = row; + mImageViews = imageViews; + } + + @Override + public void setBitmap(BitmapHolder bitmapHolder, Conaco.Source source) { + bitmapHolder.obtain(); + Bitmap bitmap = bitmapHolder.getBitmap(); + int maxWidth = bitmap.getWidth(); + int maxHeight = bitmap.getHeight(); + + int size = mRow.itemArray.size(); + for(int i = 0; i < size; i++) { + Item item = mRow.itemArray.get(i); + if (item.xOffset + item.width > maxWidth) { + item.width = maxWidth - item.xOffset; + if (item.width <= 0) { + // Bad area + continue; + } + } + if (item.yOffset + item.height > maxHeight) { + item.height = maxHeight - item.yOffset; + if (item.height <= 0) { + // Bad area + continue; + } + } + + Bitmap previewBitmap = Bitmap.createBitmap(bitmap, item.xOffset, + item.yOffset, item.width, item.height); + + if (source != Conaco.Source.MEMORY) { + Drawable[] layers = new Drawable[2]; + layers[0] = new ColorDrawable(Color.TRANSPARENT); + layers[1] = new BitmapDrawable(mImageViews[i].getResources(), previewBitmap); + TransitionDrawable transitionDrawable = new TransitionDrawable(layers); + mImageViews[i].setImageDrawable(transitionDrawable); + transitionDrawable.startTransition(300); + } else { + mImageViews[i].setImageBitmap(previewBitmap); + } + } + + bitmapHolder.release(); + + mLoadImageTasks.remove(this); + } + + @Override + public void setDrawable(Drawable drawable) { + // Empty + } + + @Override + public void setTaskId(int id) { + mTaskId = id; + } + + @Override + public int getTaskId() { + return mTaskId; + } + + @Override + public void onFailure() { + // TODO + mLoadImageTasks.remove(this); + } + + @Override + public void onCancel() { + // Empty + } + } + + class Row { + + public String imageUrl; + public List itemArray = new ArrayList<>(); + public int imageIndex; + + public Row(String imageUrl) { + this.imageUrl = imageUrl; + + int index1 = imageUrl.lastIndexOf('-'); + int index2 = imageUrl.lastIndexOf('.'); + + try { + imageIndex = Integer.parseInt(imageUrl.substring(index1 + 1, index2)) - 1; + } catch (Exception e) { + Log.w("Can't get normal preview index", e); + imageIndex = -1; + } + } + + public void addItem(int xOffset, int yOffset, int width, int height, + String url) { + itemArray.add(new Item(xOffset, yOffset, width, height, url)); + } + } + + class Item { + + public int xOffset; + public int yOffset; + public int width; + public int height; + public String url; + + public Item(int xOffset, int yOffset, int width, int height, String url) { + this.xOffset = xOffset; + this.yOffset = yOffset; + this.width = width; + this.height = height; + this.url = url; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/PreviewSet.java b/app/src/main/java/com/hippo/ehviewer/client/data/PreviewSet.java new file mode 100644 index 000000000..0318fea5d --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/data/PreviewSet.java @@ -0,0 +1,68 @@ +/* + * 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.ehviewer.client.data; + +import android.view.LayoutInflater; + +import com.hippo.conaco.Conaco; +import com.hippo.ehviewer.R; +import com.hippo.widget.SimpleGridLayout; + +public abstract class PreviewSet { + + private int mStartIndex; + private int mGid; + + public void setStartIndex(int startIndex) { + mStartIndex = startIndex; + } + + public int getStartIndex() { + return mStartIndex; + } + + public abstract int size(); + + public void setGid(int gid) { + mGid = gid; + } + + public int getGid() { + return mGid; + } + + public abstract void bindView(SimpleGridLayout simpleGridLayout, + LayoutInflater inflater, Conaco conaco); + + protected void ensureViewGroup(SimpleGridLayout simpleGridLayout, LayoutInflater inflater) { + int size = size(); + int diff = size - simpleGridLayout.getChildCount(); + + if (diff > 0) { + for (int i = 0; i < diff; i++) { + inflater.inflate(R.layout.item_preview, simpleGridLayout, true); + } + } else if (diff < 0) { + diff = -diff; + for (int i = 0; i < diff; i++) { + simpleGridLayout.removeViews(size, diff); + } + } + } + + public abstract void cancelLoadTask(SimpleGridLayout simpleGridLayout, Conaco conaco); +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/TagGroup.java b/app/src/main/java/com/hippo/ehviewer/client/data/TagGroup.java similarity index 96% rename from app/src/main/java/com/hippo/ehviewer/data/TagGroup.java rename to app/src/main/java/com/hippo/ehviewer/client/data/TagGroup.java index ef49c543e..20e8de1c0 100644 --- a/app/src/main/java/com/hippo/ehviewer/data/TagGroup.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/TagGroup.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.data; +package com.hippo.ehviewer.client.data; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/hippo/ehviewer/data/UnsupportedSearchException.java b/app/src/main/java/com/hippo/ehviewer/client/data/UnsupportedSearchException.java similarity index 95% rename from app/src/main/java/com/hippo/ehviewer/data/UnsupportedSearchException.java rename to app/src/main/java/com/hippo/ehviewer/client/data/UnsupportedSearchException.java index 6bae76ea3..a99b5092c 100644 --- a/app/src/main/java/com/hippo/ehviewer/data/UnsupportedSearchException.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/UnsupportedSearchException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.data; +package com.hippo.ehviewer.client.data; public class UnsupportedSearchException extends Exception { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/CommentScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/CommentScene.java index 808c02d0e..c945ba393 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/CommentScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/CommentScene.java @@ -13,7 +13,7 @@ import android.widget.TextView; import com.hippo.ehviewer.EhApplication; import com.hippo.ehviewer.R; -import com.hippo.ehviewer.data.Comment; +import com.hippo.ehviewer.client.data.Comment; import com.hippo.rippleold.RippleSalon; import com.hippo.scene.Announcer; import com.hippo.scene.AppbarScene; 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 d65660fdf..01c0851c1 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 @@ -39,26 +39,36 @@ import com.hippo.ehviewer.EhApplication; import com.hippo.ehviewer.EhImageKeyFactory; import com.hippo.ehviewer.R; import com.hippo.ehviewer.client.EhClient; -import com.hippo.ehviewer.data.Comment; -import com.hippo.ehviewer.data.GalleryDetail; -import com.hippo.ehviewer.data.GalleryInfo; -import com.hippo.ehviewer.data.TagGroup; +import com.hippo.ehviewer.client.EhException; +import com.hippo.ehviewer.client.OffensiveException; +import com.hippo.ehviewer.client.data.Comment; +import com.hippo.ehviewer.client.data.GalleryDetail; +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.ehviewer.client.data.TagGroup; import com.hippo.ehviewer.util.Config; import com.hippo.ehviewer.util.EhUtils; import com.hippo.ehviewer.widget.LoadImageView; +import com.hippo.ehviewer.widget.PreviewLayout; import com.hippo.ehviewer.widget.RatingView; import com.hippo.miscellaneous.StartActivityHelper; import com.hippo.rippleold.RippleSalon; import com.hippo.scene.Announcer; +import com.hippo.scene.RippleCurtain; import com.hippo.scene.Scene; +import com.hippo.scene.SimpleDialog; import com.hippo.util.Log; import com.hippo.util.URLImageGetter; import com.hippo.util.UiUtils; +import com.hippo.util.ViewUtils; +import com.hippo.widget.AccurateClick; import com.hippo.widget.AutoWrapLayout; import java.util.List; -public class GalleryDetailScene extends Scene implements View.OnClickListener { +import static com.hippo.ehviewer.R.id.no_tags; + +public class GalleryDetailScene extends Scene implements View.OnClickListener, + AccurateClick.OnAccurateClickListener { public static final String KEY_GALLERY_INFO = "gallery_info"; @@ -88,8 +98,10 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { private TextView mShare; private TextView mRate; private LinearLayout mTag; + private TextView mNoTags; private LinearLayout mComment; private TextView mCommentMore; + private PreviewLayout mPreviewLayout; private View mProgressBar; @@ -97,6 +109,12 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { private GalleryDetail mGalleryDetail; + private void requestGalleryDetail(GalleryInfo gi) { + EhClient.getInstance().getGalleryDetail(Config.getEhSource(), + EhClient.getDetailUrl(Config.getEhSource(), gi.gid, gi.token, 0), + new EhDetailListener()); + } + private void handleAnnouncer(Announcer announcer) { GalleryInfo gi; @@ -112,9 +130,7 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { mCategory.setText(EhUtils.getCategory(gi.category)); mCategory.setTextColor(EhUtils.getCategoryColor(gi.category)); - EhClient.getInstance().getGalleryDetail(Config.getEhSource(), - EhClient.getDetailUrl(Config.getEhSource(), gi.gid, gi.token, 0), - new EhDetailListener()); + requestGalleryDetail(gi); } Context context = getStageActivity(); @@ -146,7 +162,8 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { mTorrent.setOnClickListener(this); mShare.setOnClickListener(this); mRate.setOnClickListener(this); - mCommentMore.setOnClickListener(this); + + AccurateClick.setOnAccurateClickListener(mComment, this); RippleSalon.addRipple(mRead, false); RippleSalon.addRipple(mDownload, false); @@ -187,8 +204,10 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { mShare = (TextView) mContent.findViewById(R.id.share); mRate = (TextView) mContent.findViewById(R.id.rate); mTag = (LinearLayout) mContent.findViewById(R.id.tag); + mNoTags = (TextView) mTag.findViewById(no_tags); mComment = (LinearLayout) mContent.findViewById(R.id.comment); mCommentMore = (TextView) mContent.findViewById(R.id.comment_more); + mPreviewLayout = (PreviewLayout) mContent.findViewById(R.id.preview); mProgressBar = findViewById(R.id.progress_bar); @@ -220,10 +239,23 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { } else if (v == mShare) { StartActivityHelper.share(getStageActivity(), EhClient.getDetailUrl(Config.getEhSource(), gd.gid, gd.token, 0)); - } else if (v == mCommentMore) { + } + } + + @Override + public void onAccurateClick(View v, float x, float y) { + GalleryDetail gd = mGalleryDetail; + if (gd == null) { + return; + } + + if (v == mComment) { + int[] position = new int[2]; + ViewUtils.getLocationInAncestor(mComment, position, getSceneView()); Announcer announcer = new Announcer(); announcer.setObject(mGalleryDetail.comments); - startScene(CommentScene.class, announcer); + startScene(CommentScene.class, announcer, + new RippleCurtain((int) x + position[0], (int) y + position[1])); } } @@ -272,31 +304,34 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { } private void fillTags(List tags) { - Context context = getStageActivity(); - LayoutInflater inflater = getStageActivity().getLayoutInflater(); - int x = UiUtils.dp2pix(context, 2); - int y = UiUtils.dp2pix(context, 4); + if (tags.size() != 0) { + mNoTags.setVisibility(View.GONE); - for (TagGroup tagGroup : tags) { - LinearLayout tagGroupLayout = new LinearLayout(context); - tagGroupLayout.setOrientation(LinearLayout.HORIZONTAL); - AutoWrapLayout tagLayout = new AutoWrapLayout(context); + Context context = getStageActivity(); + LayoutInflater inflater = getStageActivity().getLayoutInflater(); + int x = UiUtils.dp2pix(context, 2); + int y = UiUtils.dp2pix(context, 4); - // Group name - @SuppressLint("InflateParams") - TextView groupNameView = (TextView) inflater.inflate(R.layout.tag_group, null); - groupNameView.setText(tagGroup.groupName); - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - lp.setMargins(x, y, x, y); - tagGroupLayout.addView(groupNameView, lp); + for (TagGroup tagGroup : tags) { + LinearLayout tagGroupLayout = new LinearLayout(context); + tagGroupLayout.setOrientation(LinearLayout.HORIZONTAL); + AutoWrapLayout tagLayout = new AutoWrapLayout(context); - int count = tagGroup.getTagCount(); - for (int i = 0; i < count; i++) { + // Group name @SuppressLint("InflateParams") - TextView tagView = (TextView) inflater.inflate(R.layout.tag, null); - tagView.setText(tagGroup.getTagAt(i)); + TextView groupNameView = (TextView) inflater.inflate(R.layout.tag_group, null); + groupNameView.setText(tagGroup.groupName); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + lp.setMargins(x, y, x, y); + tagGroupLayout.addView(groupNameView, lp); + + int count = tagGroup.getTagCount(); + for (int i = 0; i < count; i++) { + @SuppressLint("InflateParams") + TextView tagView = (TextView) inflater.inflate(R.layout.tag, null); + tagView.setText(tagGroup.getTagAt(i)); /* tagView.setOnClickListener(new View.OnClickListener() { @Override @@ -312,17 +347,21 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { } }); */ - AutoWrapLayout.LayoutParams alp = new AutoWrapLayout.LayoutParams(); - alp.setMargins(x, y, x, y); - tagLayout.addView(tagView, alp); + AutoWrapLayout.LayoutParams alp = new AutoWrapLayout.LayoutParams(); + alp.setMargins(x, y, x, y); + tagLayout.addView(tagView, alp); + } + tagGroupLayout.addView(tagLayout); + mTag.addView(tagGroupLayout); } - tagGroupLayout.addView(tagLayout); - mTag.addView(tagGroupLayout); + } else { + mNoTags.setVisibility(View.VISIBLE); } } + @SuppressLint("InflateParams") private View createCommentView(Comment comment) { - View view = LayoutInflater.from(getStageActivity()).inflate(R.layout.item_comment, null); + View view = LayoutInflater.from(getStageActivity()).inflate(R.layout.item_comment, null); ((TextView) view.findViewById(R.id.user)).setText(comment.user); ((TextView) view.findViewById(R.id.time)).setText(comment.time); TextView commentText = (TextView) view.findViewById(R.id.comment); @@ -364,11 +403,13 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { int commentNum = galleryDetail.comments.size(); if (commentNum == 0) { - mCommentMore.setText("暂无评论"); + mCommentMore.setText(R.string.no_comments); + // Remove padding interval + mCommentMore.setPadding(0, 0, 0, 0); } else if (commentNum <= 2){ - mCommentMore.setText("暂无更多评论"); + mCommentMore.setText(R.string.no_more_comments); } else { - mCommentMore.setText("更多评论"); + mCommentMore.setText(R.string.more_comment); } int maxShown = Math.min(2, commentNum); for (int i = 0; i < maxShown; i++) { @@ -377,13 +418,30 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener { LinearLayout.LayoutParams.WRAP_CONTENT)); } + mPreviewLayout.setData(galleryDetail.previewSetArray, + getStageActivity().getLayoutInflater(), + EhApplication.getConaco(getStageActivity())); + mPreviewLayout.selectPreviewAt(0); + mViewTransition.showView(0); } @Override public void onFailure(Exception e) { e.printStackTrace(); - // TODO + if (e instanceof OffensiveException) { + + } else if (e instanceof EhException) { + new SimpleDialog.Builder(getStageActivity()).setTitle("Error") + .setMessage(e.getMessage()) + .setNegativeButton(android.R.string.cancel) + .setOnCloseListener(new SimpleDialog.OnCloseListener() { + @Override + public void onClose(SimpleDialog dialog, boolean cancel) { + finish(); + } + }).show(); + } } } } 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 e76e39fcb..5bb10b54c 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 @@ -47,9 +47,9 @@ import com.hippo.ehviewer.EhImageKeyFactory; import com.hippo.ehviewer.R; import com.hippo.ehviewer.client.EhClient; import com.hippo.ehviewer.client.GalleryListParser; -import com.hippo.ehviewer.data.GalleryInfo; -import com.hippo.ehviewer.data.ListUrlBuilder; -import com.hippo.ehviewer.data.UnsupportedSearchException; +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.ehviewer.client.data.ListUrlBuilder; +import com.hippo.ehviewer.client.data.UnsupportedSearchException; import com.hippo.ehviewer.ui.ContentActivity; import com.hippo.ehviewer.util.Config; import com.hippo.ehviewer.util.EhUtils; diff --git a/app/src/main/java/com/hippo/ehviewer/widget/LoadImageView.java b/app/src/main/java/com/hippo/ehviewer/widget/LoadImageView.java index 35af0832d..2df81bb8f 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/LoadImageView.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/LoadImageView.java @@ -7,13 +7,13 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.util.AttributeSet; -import android.widget.ImageView; import com.hippo.conaco.BitmapHolder; import com.hippo.conaco.Conaco; import com.hippo.conaco.Unikery; +import com.hippo.widget.FixedAspectImageView; -public class LoadImageView extends ImageView implements Unikery { +public class LoadImageView extends FixedAspectImageView implements Unikery { private int mTaskId = Unikery.INVAILD_ID; diff --git a/app/src/main/java/com/hippo/ehviewer/widget/PreviewLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/PreviewLayout.java new file mode 100644 index 000000000..40e761484 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/PreviewLayout.java @@ -0,0 +1,220 @@ +/* + * 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.ehviewer.widget; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.hippo.conaco.Conaco; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.client.data.LargePreviewSet; +import com.hippo.ehviewer.client.data.PreviewSet; +import com.hippo.rippleold.RippleSalon; +import com.hippo.widget.SimpleGridLayout; + +public final class PreviewLayout extends LinearLayout implements View.OnClickListener { + + private GestureDetector mGestureDetector; + private PreviewSet[] mPreviewSets; + private Conaco mConaco; + private LayoutInflater mInflater; + private int mPreviewSetCount; + private int mCurrentPreviewIndex = -1; + + private SimpleGridLayout mSimpleGridLayout; + private View mTopPrevious; + private TextView mTopSelection; + private View mTopNext; + private View mBottomPrevious; + private TextView mBottomSelection; + private View mBottomNext; + + private int mWidth; + + public PreviewLayout(Context context) { + super(context); + init(context); + } + + public PreviewLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public PreviewLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + setOrientation(LinearLayout.VERTICAL); + + LayoutInflater.from(context).inflate(R.layout.widget_preview_layout, this); + + ViewGroup viewGroup = (ViewGroup) getChildAt(0); + mTopPrevious = viewGroup.getChildAt(0); + mTopSelection = (TextView) viewGroup.getChildAt(2); + mTopNext = viewGroup.getChildAt(4); + mSimpleGridLayout = (SimpleGridLayout) getChildAt(1); + viewGroup = (ViewGroup) getChildAt(2); + mBottomPrevious = viewGroup.getChildAt(0); + mBottomSelection = (TextView) viewGroup.getChildAt(2); + mBottomNext = viewGroup.getChildAt(4); + + RippleSalon.addRipple(mTopPrevious, false); + RippleSalon.addRipple(mTopSelection, false); + RippleSalon.addRipple(mTopNext, false); + RippleSalon.addRipple(mBottomPrevious, false); + RippleSalon.addRipple(mBottomSelection, false); + RippleSalon.addRipple(mBottomNext, false); + + mTopPrevious.setOnClickListener(this); + mTopSelection.setOnClickListener(this); + mTopNext.setOnClickListener(this); + mBottomPrevious.setOnClickListener(this); + mBottomSelection.setOnClickListener(this); + mBottomNext.setOnClickListener(this); + + GestureListener gestureListener = new GestureListener(); + mGestureDetector = new GestureDetector(context, gestureListener); + mGestureDetector.setIsLongpressEnabled(false); + } + + public void setData(PreviewSet[] previewSets, LayoutInflater inflater, Conaco conaco) { + mPreviewSets = previewSets; + mPreviewSetCount = previewSets.length; + mInflater = inflater; + mConaco = conaco; + } + + private boolean isLargePreview() { + for (PreviewSet previewSet : mPreviewSets) { + if (previewSet != null) { + return previewSet instanceof LargePreviewSet; + } + } + return false; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + int width = r - l; + if (mWidth != width) { + mWidth = width; + int itemWidth = getResources().getDimensionPixelOffset( + isLargePreview() ? R.dimen.gallery_large_preview_item_width : + R.dimen.gallery_normal_preview_item_width); + int columnCount = Math.round( + (width - getPaddingLeft() - getPaddingRight()) / (float) itemWidth); + columnCount = Math.max(columnCount, 1); + mSimpleGridLayout.setColumnCount(columnCount); + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return handleTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(@NonNull MotionEvent event) { + return handleTouchEvent(event); + } + + private boolean handleTouchEvent(MotionEvent event) { + return mGestureDetector.onTouchEvent(event); + } + + public int getPreviewPageCount() { + return mPreviewSetCount; + } + + public void selectPreviewAt(int index) { + if (index < 0 || index >= mPreviewSetCount) { + throw new IndexOutOfBoundsException("Invalid index " + index + ", size is " + mPreviewSetCount); + } + + mCurrentPreviewIndex = index; + + PreviewSet previewSet = mPreviewSets[index]; + if (previewSet != null) { + previewSet.bindView(mSimpleGridLayout, mInflater, mConaco); + } else { + // TODO + } + + String showText = (index + 1) + "/" + mPreviewSetCount; + mTopSelection.setText(showText); + mBottomSelection.setText(showText); + } + + public void tryNextPreview() { + if (mCurrentPreviewIndex < mPreviewSetCount - 1) { + selectPreviewAt(mCurrentPreviewIndex + 1); + } + } + + public void tryPreviousPreview() { + if (mCurrentPreviewIndex > 0) { + selectPreviewAt(mCurrentPreviewIndex - 1); + } + } + + @Override + public void onClick(View v) { + + } + + class GestureListener extends GestureDetector.SimpleOnGestureListener { + + private boolean mHasScrollX = false; + + @Override + public boolean onDown(MotionEvent e) { + mHasScrollX = false; + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + if (Math.abs(distanceX) > Math.abs(distanceY)) { + if (!mHasScrollX) { + mHasScrollX = true; + if (distanceX > 0) { + tryNextPreview(); + } else { + tryPreviousPreview(); + } + } + return true; + } else { + return false; + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java index f1cd0c92e..6a46f40e8 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java @@ -36,7 +36,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.hippo.ehviewer.R; -import com.hippo.ehviewer.data.ListUrlBuilder; +import com.hippo.ehviewer.client.data.ListUrlBuilder; import com.hippo.rippleold.RippleSalon; import com.hippo.scene.SimpleDialog; import com.hippo.util.UiUtils; diff --git a/app/src/main/java/com/hippo/scene/RippleCurtain.java b/app/src/main/java/com/hippo/scene/RippleCurtain.java new file mode 100644 index 000000000..0121c53e8 --- /dev/null +++ b/app/src/main/java/com/hippo/scene/RippleCurtain.java @@ -0,0 +1,155 @@ +package com.hippo.scene; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.os.Build; +import android.support.annotation.NonNull; +import android.view.View; +import android.view.ViewTreeObserver; + +import com.hippo.animation.DurationInterpolator; +import com.hippo.animation.SimpleAnimatorListener; +import com.hippo.util.Log; +import com.hippo.util.ViewUtils; + +import java.util.HashSet; +import java.util.Set; + +public class RippleCurtain extends Curtain { + + private static final String TAG = RippleCurtain.class.getSimpleName(); + + private static long ANIMATE_TIME = 300L; + + private int mStartX; + private int mStartY; + + private AnimatorSet mAnimatorSet; + + public RippleCurtain(int startX, int startY) { + mStartX = startX; + mStartY = startY; + } + + @Override + protected void onRebirth() { + mStartX = 0; + mStartY = 0; + } + + @Override + public void open(@NonNull final Scene enter, @NonNull final Scene exit) { + final Set animatorCollection = new HashSet<>(); + + final SceneView enterView = enter.getSceneView(); + enterView.setStartPoint(mStartX, mStartY); + ViewUtils.setVisibility(enterView, View.INVISIBLE); + + // Canvas.clipPath() not work for pre-JELLY_BEAN_MR2 when hardware accelerated + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + ViewUtils.removeHardwareAccelerationSupport(enterView); + } + + enterView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + ViewUtils.removeOnGlobalLayoutListener(enterView.getViewTreeObserver(), this); + + final View sceneContentView = enter.getSceneView().getChildAt(0); + if (sceneContentView != null) { + sceneContentView.setVisibility(View.INVISIBLE); + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(sceneContentView, "alpha", 0f, 1f); + alphaAnim.setDuration(ANIMATE_TIME * 2); + alphaAnim.setInterpolator(new DurationInterpolator(0.3f)); + alphaAnim.addListener(new SimpleAnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + sceneContentView.setVisibility(View.VISIBLE); + } + }); + animatorCollection.add(alphaAnim); + } else { + Log.w(TAG, "Can't get enter scene content view"); + } + + PropertyValuesHolder circlePercentPvh = PropertyValuesHolder.ofFloat("circlePercent", 0f, 1f); + ObjectAnimator animFrame = ObjectAnimator.ofPropertyValuesHolder(enterView, circlePercentPvh); + animFrame.setDuration(ANIMATE_TIME); + animatorCollection.add(animFrame); + + mAnimatorSet = new AnimatorSet(); + mAnimatorSet.playTogether(animatorCollection); + mAnimatorSet.addListener(new SimpleAnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + ViewUtils.setVisibility(enterView, View.VISIBLE); + } + @Override + public void onAnimationEnd(Animator animation) { + dispatchOpenFinished(enter, exit); + hideSceneOnOpen(exit); + mAnimatorSet = null; + + // Canvas.clipPath() not work for pre-JELLY_BEAN_MR2 when hardware accelerated + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + ViewUtils.addHardwareAccelerationSupport(enterView); + } + } + }); + mAnimatorSet.start(); + } + }); + } + + @Override + public void close(@NonNull final Scene enter, @NonNull final Scene exit) { + showSceneOnClose(enter); + + final Set animatorCollection = new HashSet<>(); + + final SceneView exitView = exit.getSceneView(); + exitView.setStartPoint(mStartX, mStartY); + + // Canvas.clipPath() not work for pre-JELLY_BEAN_MR2 when hardware accelerated + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + ViewUtils.removeHardwareAccelerationSupport(exitView); + } + + PropertyValuesHolder circlePercentPvh = PropertyValuesHolder.ofFloat("circlePercent", 1f, 0f); + ObjectAnimator animFrame = ObjectAnimator.ofPropertyValuesHolder(exitView, circlePercentPvh); + animFrame.setDuration(ANIMATE_TIME); + animatorCollection.add(animFrame); + + mAnimatorSet = new AnimatorSet(); + mAnimatorSet.playTogether(animatorCollection); + mAnimatorSet.addListener(new SimpleAnimatorListener() { + @Override + public void onAnimationEnd(Animator animation, boolean canceled) { + super.onAnimationEnd(animation, canceled); + dispatchCloseFinished(enter, exit); + mAnimatorSet = null; + + // Canvas.clipPath() not work for pre-JELLY_BEAN_MR2 when hardware accelerated + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + ViewUtils.addHardwareAccelerationSupport(exitView); + } + } + }); + mAnimatorSet.start(); + } + + @Override + public void endAnimation() { + if (mAnimatorSet != null) { + mAnimatorSet.end(); + } + } + + @Override + public boolean isInAnimation() { + return mAnimatorSet != null; + } +} diff --git a/app/src/main/java/com/hippo/scene/SceneView.java b/app/src/main/java/com/hippo/scene/SceneView.java index 600a53d95..d09a4ba23 100644 --- a/app/src/main/java/com/hippo/scene/SceneView.java +++ b/app/src/main/java/com/hippo/scene/SceneView.java @@ -17,6 +17,8 @@ package com.hippo.scene; import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Path; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; @@ -27,6 +29,12 @@ public class SceneView extends AbsoluteLayout { private boolean mEnableTouch = true; + private Path mPath; + private float mRadius; + private float mCirclePercent = 1f; + private float mStartX = -1f; + private float mStartY = -1f; + public SceneView(Context context) { super(context); init(); @@ -44,6 +52,7 @@ public class SceneView extends AbsoluteLayout { private void init() { setClickable(true); + mPath = new Path(); } public void setEnableTouch(boolean enableTouch) { @@ -64,4 +73,65 @@ public class SceneView extends AbsoluteLayout { public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event) { return mEnableTouch && super.dispatchGenericMotionEvent(event); } + + private void updateRadius() { + int width = getWidth(); + int height = getHeight(); + if ((mStartX != -1f || mStartY != -1f) && width != 0 && height != 0) { + float h = Math.max(mStartX, width - mStartX); + float v = Math.max(mStartY, height - mStartY); + mRadius = (float) Math.hypot(h, v); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, + int r, int b) { + super.onLayout(changed, l, t, r, b); + updateRadius(); + } + + @Override + public void draw(@NonNull Canvas canvas) { + float circlePercent = mCirclePercent; + int saved = 0; + + if (circlePercent != 1f && mRadius != 0f) { + mPath.rewind(); + mPath.addCircle(mStartX, mStartY, circlePercent * mRadius, Path.Direction.CW); + saved = canvas.save(); + canvas.clipPath(mPath); + } + + super.draw(canvas); + + if (circlePercent != 1f && mRadius != 0f && saved != 0) { + canvas.restoreToCount(saved); + } + } + + @SuppressWarnings("unused") + public void setCirclePercent(float circlePercent) { + float largeCirclePercent = Math.max(mCirclePercent, circlePercent); + mCirclePercent = circlePercent; + + if (largeCirclePercent == 1f) { + invalidate(); + } else { + float radius = mRadius * largeCirclePercent; + invalidate((int) (mStartX - radius), (int) (mStartY - radius), + (int) (mStartX + radius), (int) (mStartY + radius)); + } + } + + @SuppressWarnings("unused") + public float getCirclePercent() { + return mCirclePercent; + } + + public void setStartPoint(float x, float y) { + mStartX = x; + mStartY = y; + updateRadius(); + } } diff --git a/app/src/main/java/com/hippo/scene/SimpleDialogCurtain.java b/app/src/main/java/com/hippo/scene/SimpleDialogCurtain.java index d453eeb99..8c3c40e65 100644 --- a/app/src/main/java/com/hippo/scene/SimpleDialogCurtain.java +++ b/app/src/main/java/com/hippo/scene/SimpleDialogCurtain.java @@ -157,7 +157,7 @@ public class SimpleDialogCurtain extends Curtain { final Set animatorCollection = new HashSet<>(); // Handle background - int bgColor = enter.getBackgroundColor(); + int bgColor = exitDialog.getBackgroundColor(); int endBgColor = bgColor & 0xffffff; ObjectAnimator colorAnim = ObjectAnimator.ofInt(exit, "backgroundColor", endBgColor); colorAnim.setEvaluator(ArgbEvaluator.getInstance()); diff --git a/app/src/main/java/com/hippo/util/MathUtils.java b/app/src/main/java/com/hippo/util/MathUtils.java index 569337072..49c1a241b 100644 --- a/app/src/main/java/com/hippo/util/MathUtils.java +++ b/app/src/main/java/com/hippo/util/MathUtils.java @@ -274,6 +274,16 @@ public final class MathUtils { return n + 1; } + /** + * + * @param a + * @param b + * @return + */ + public static int ceilDivide(int a, int b) { + return (a + b - 1) / b; + } + /** * Return is a and b is in the same side to standard. * If one of a and b equals standard, return true. diff --git a/app/src/main/java/com/hippo/util/ViewUtils.java b/app/src/main/java/com/hippo/util/ViewUtils.java index 46cf42b5f..babdc5470 100644 --- a/app/src/main/java/com/hippo/util/ViewUtils.java +++ b/app/src/main/java/com/hippo/util/ViewUtils.java @@ -28,6 +28,8 @@ import android.view.ViewTreeObserver; public final class ViewUtils { + public static final int MAX_SIZE = Integer.MAX_VALUE & ~(0x3 << 30); + /** * Get view center location in window * diff --git a/app/src/main/java/com/hippo/widget/AccurateClick.java b/app/src/main/java/com/hippo/widget/AccurateClick.java new file mode 100644 index 000000000..a980b150c --- /dev/null +++ b/app/src/main/java/com/hippo/widget/AccurateClick.java @@ -0,0 +1,64 @@ +/* + * 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.support.annotation.NonNull; +import android.view.View; + +import com.hippo.ehviewer.R; +import com.hippo.hotspot.HotspotTouchHelper; +import com.hippo.hotspot.Hotspotable; + +public final class AccurateClick { + + public static void setOnAccurateClickListener(@NonNull View view, @NonNull OnAccurateClickListener listener) { + AccurateClickHelper accurateClickHelper = new AccurateClickHelper(); + view.setTag(R.id.view_tag_accurate_click_helper, accurateClickHelper); + view.setTag(R.id.view_tag_on_accurate_click_listener, listener); + view.setOnTouchListener(new HotspotTouchHelper(accurateClickHelper)); + view.setOnClickListener(new DelegateClickListener()); + } + + static class DelegateClickListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + Object accurateClickHelper = v.getTag(R.id.view_tag_accurate_click_helper); + Object listener = v.getTag(R.id.view_tag_on_accurate_click_listener); + if (accurateClickHelper instanceof AccurateClickHelper && listener instanceof OnAccurateClickListener) { + AccurateClickHelper ach = (AccurateClickHelper) accurateClickHelper; + ((OnAccurateClickListener) listener).onAccurateClick(v, ach.x, ach.y); + } + } + } + + static class AccurateClickHelper implements Hotspotable { + + public float x; + public float y; + + @Override + public void setHotspot(float x, float y) { + this.x = x; + this.y = y; + } + } + + public interface OnAccurateClickListener { + void onAccurateClick(View v, float x, float y); + } +} diff --git a/app/src/main/java/com/hippo/widget/CardButton.java b/app/src/main/java/com/hippo/widget/CardButton.java index 7e8b87c91..f81c3a0ac 100644 --- a/app/src/main/java/com/hippo/widget/CardButton.java +++ b/app/src/main/java/com/hippo/widget/CardButton.java @@ -43,6 +43,13 @@ public class CardButton extends Button implements CardViewDelegate { // Empty } + @Override + public void setPadding(int left, int top, int right, int bottom) { + if (mCardHelper != null) { + mCardHelper.setPadding(left, top, right, bottom); + } + } + @Override public void setRawPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, bottom); diff --git a/app/src/main/java/com/hippo/widget/CardLinearLayout.java b/app/src/main/java/com/hippo/widget/CardLinearLayout.java index 93978f29e..78eb1342b 100644 --- a/app/src/main/java/com/hippo/widget/CardLinearLayout.java +++ b/app/src/main/java/com/hippo/widget/CardLinearLayout.java @@ -45,7 +45,9 @@ public class CardLinearLayout extends LinearLayout implements CardViewDelegate { @Override public void setPadding(int left, int top, int right, int bottom) { - mCardHelper.setPadding(left, top, right, bottom); + if (mCardHelper != null) { + mCardHelper.setPadding(left, top, right, bottom); + } } @Override 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..661deb3cf --- /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.util.MathUtils; +import com.hippo.util.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.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_chevron_left.xml b/app/src/main/res/drawable/ic_chevron_left.xml new file mode 100644 index 000000000..b8969b2ff --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron_left.xml @@ -0,0 +1,40 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_chevron_right.xml b/app/src/main/res/drawable/ic_chevron_right.xml new file mode 100644 index 000000000..2fc4d8d2b --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron_right.xml @@ -0,0 +1,41 @@ + + + + + + + + diff --git a/app/src/main/res/layout-v21/scene_gallery_detail.xml b/app/src/main/res/layout-v21/scene_gallery_detail.xml index 4654b0f5a..089bf0011 100644 --- a/app/src/main/res/layout-v21/scene_gallery_detail.xml +++ b/app/src/main/res/layout-v21/scene_gallery_detail.xml @@ -205,43 +205,43 @@ android:orientation="vertical" android:paddingLeft="32dp" android:paddingRight="32dp" - android:paddingTop="16dp"/> - - - - + android:paddingBottom="16dp"> + android:textColor="@color/theme_accent" + android:text="@string/no_tags"/> + + + + + + diff --git a/app/src/main/res/layout/gallery_detail_comment.xml b/app/src/main/res/layout/gallery_detail_comment.xml new file mode 100644 index 000000000..ba729b847 --- /dev/null +++ b/app/src/main/res/layout/gallery_detail_comment.xml @@ -0,0 +1,36 @@ + + + + + + + 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..a29fc42d5 --- /dev/null +++ b/app/src/main/res/layout/item_preview.xml @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/preview_controller.xml b/app/src/main/res/layout/preview_controller.xml new file mode 100644 index 000000000..6db78cf6f --- /dev/null +++ b/app/src/main/res/layout/preview_controller.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/scene_comment.xml b/app/src/main/res/layout/scene_comment.xml index 5fdf8874f..790ed7e4a 100644 --- a/app/src/main/res/layout/scene_comment.xml +++ b/app/src/main/res/layout/scene_comment.xml @@ -27,6 +27,9 @@ android:id="@+id/reply" style="@style/FloatingActionButton" android:layout_width="wrap_content" - android:layout_height="wrap_content"/> + android:layout_height="wrap_content" + android:layout_gravity="right|bottom" + android:layout_marginRight="@dimen/keyline_margin" + android:layout_marginBottom="16dp"/> diff --git a/app/src/main/res/layout/widget_preview_layout.xml b/app/src/main/res/layout/widget_preview_layout.xml new file mode 100644 index 000000000..8605173ed --- /dev/null +++ b/app/src/main/res/layout/widget_preview_layout.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3bcd4b3df..0ed6fa36e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -60,6 +60,30 @@ 请输入数字 当前页面无效 + + %d 页 + %d \u2665 + 更多信息 + 大家感觉 %s + 暂无评论 + 暂无更多评论 + 更多评论 + + + 根本把持不住 + 好极了 + 很棒 + 不错 + 还行 + 一般般 + 不行 + 糟糕 + 瞎眼 + 快要窒息了 + … + + 无法找到指定 Activity + 超时 找不到主机 @@ -101,4 +125,10 @@ 用户名 密码 + 阅读 + 种子 + 分享 + + 评论 + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 476bd5882..9a77babc5 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 cc661e167..f6249ee22 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -130,6 +130,9 @@ 20dp 2dp + 96dp + 128dp + 24sp 22sp diff --git a/app/src/main/res/values/id.xml b/app/src/main/res/values/id.xml new file mode 100644 index 000000000..b29a98451 --- /dev/null +++ b/app/src/main/res/values/id.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/app/src/main/res/values/pathdata.xml b/app/src/main/res/values/pathdata.xml index 3ffa905fe..c26b0e3b8 100644 --- a/app/src/main/res/values/pathdata.xml +++ b/app/src/main/res/values/pathdata.xml @@ -38,5 +38,7 @@ M19,14.5C19,14.5 21,16.67 21,18A2,2 0 0,1 19,20A2,2 0 0,1 17,18C17,16.67 19,14.5 19,14.5M5,18V9A2,2 0 0,1 3,7A2,2 0 0,1 5,5V4A2,2 0 0,1 7,2H9A2,2 0 0,1 11,4V5H19A2,2 0 0,1 21,7V9L21,11A1,1 0 0,1 22,12A1,1 0 0,1 21,13H17A1,1 0 0,1 16,12A1,1 0 0,1 17,11V9H11V18H12A2,2 0 0,1 14,20V22H2V20A2,2 0 0,1 4,18H5Z M18,16.08C17.24,16.08 16.56,16.38 16.04,16.85L8.91,12.7C8.96,12.47 9,12.24 9,12C9,11.76 8.96,11.53 8.91,11.3L15.96,7.19C16.5,7.69 17.21,8 18,8A3,3 0 0,0 21,5A3,3 0 0,0 18,2A3,3 0 0,0 15,5C15,5.24 15.04,5.47 15.09,5.7L8.04,9.81C7.5,9.31 6.79,9 6,9A3,3 0 0,0 3,12A3,3 0 0,0 6,15C6.79,15 7.5,14.69 8.04,14.19L15.16,18.34C15.11,18.55 15.08,18.77 15.08,19C15.08,20.61 16.39,21.91 18,21.91C19.61,21.91 20.92,20.61 20.92,19A2.92,2.92 0 0,0 18,16.08Z M22.5,10.5H15.75C15.13,10.5 14.6,10.88 14.37,11.41L12.11,16.7C12.04,16.87 12,17.06 12,17.25V18.5A1,1 0 0,0 13,19.5H18.18L17.5,22.68V22.92C17.5,23.23 17.63,23.5 17.83,23.72L18.62,24.5L23.56,19.56C23.83,19.29 24,18.91 24,18.5V12A1.5,1.5 0 0,0 22.5,10.5M12,6.5A1,1 0 0,0 11,5.5H5.82L6.5,2.32V2.09C6.5,1.78 6.37,1.5 6.17,1.29L5.38,0.5L0.44,5.44C0.17,5.71 0,6.09 0,6.5V13A1.5,1.5 0 0,0 1.5,14.5H8.25C8.87,14.5 9.4,14.12 9.63,13.59L11.89,8.3C11.96,8.13 12,7.94 12,7.75V6.5Z + M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z + M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ccd1e10f6..06b3e74ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,6 +82,10 @@ %d \u2665 More information It is %s + No tags + No comments + No more comments + More comments a MASTERPIECE diff --git a/cardsalon b/cardsalon index 80600fba8..834b8482b 160000 --- a/cardsalon +++ b/cardsalon @@ -1 +1 @@ -Subproject commit 80600fba814f4e9f20cf59e39d3070f507591243 +Subproject commit 834b8482bd21f93d5008220b7bffc9ca5bc49751 diff --git a/hotspot b/hotspot index 1c2b4d616..0cddb7f7c 160000 --- a/hotspot +++ b/hotspot @@ -1 +1 @@ -Subproject commit 1c2b4d616029a128ea5300b26ff85425ee055ee5 +Subproject commit 0cddb7f7cad043dd6745b3e0013abdba147198a5