diff --git a/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java b/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java
new file mode 100644
index 000000000..d830116aa
--- /dev/null
+++ b/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java
@@ -0,0 +1,53 @@
+package com.hippo.drawable;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.hippo.conaco.Conaco;
+import com.hippo.conaco.Unikery;
+import com.hippo.ehviewer.R;
+
+public class UnikeryDrawable extends WrapDrawable implements Unikery {
+
+ private int mTaskId;
+
+ private View mView;
+
+ public UnikeryDrawable(View view) {
+ mView = view;
+ }
+
+ @Override
+ public void setBitmap(Bitmap bitmap, Conaco.Source source) {
+ setDrawable(new BitmapDrawable(mView.getResources(), bitmap));
+ }
+
+ @Override
+ public void setDrawable(Drawable drawable) {
+ super.setDrawable(drawable);
+ updateBounds();
+ mView.requestLayout();
+ }
+
+ @Override
+ public void setTaskId(int id) {
+ mTaskId = id;
+ }
+
+ @Override
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ @Override
+ public void onFailure() {
+ setDrawable(mView.getResources().getDrawable(R.drawable.ic_sad));
+ }
+
+ @Override
+ public void onCancel() {
+ // Empty
+ }
+}
diff --git a/app/src/main/java/com/hippo/drawable/WrapDrawable.java b/app/src/main/java/com/hippo/drawable/WrapDrawable.java
new file mode 100644
index 000000000..29cc49460
--- /dev/null
+++ b/app/src/main/java/com/hippo/drawable/WrapDrawable.java
@@ -0,0 +1,123 @@
+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 void updateBounds() {
+ setBounds(0, 0, getIntrinsicWidth(), getIntrinsicHeight());
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mDrawable != null) {
+ mDrawable.draw(canvas);
+ }
+ }
+
+ @Override
+ public void setBounds(int left, int top, int right, int bottom) {
+ super.setBounds(left, top, right, bottom);
+
+ if (mDrawable != null) {
+ mDrawable.setBounds(left, top, right, bottom);
+ }
+ }
+
+ @Override
+ public void setBounds(Rect bounds) {
+ super.setBounds(bounds);
+
+ if (mDrawable != null) {
+ mDrawable.setBounds(bounds);
+ }
+ }
+
+ @Override
+ public void setChangingConfigurations(int configs) {
+ super.setChangingConfigurations(configs);
+
+ if (mDrawable != null) {
+ mDrawable.setChangingConfigurations(configs);
+ }
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ if (mDrawable != null) {
+ return mDrawable.getChangingConfigurations();
+ } else {
+ return super.getChangingConfigurations();
+ }
+ }
+
+ @Override
+ public void setDither(boolean dither) {
+ super.setDither(dither);
+
+ if (mDrawable != null) {
+ mDrawable.setDither(dither);
+ }
+ }
+
+ @Override
+ public void setFilterBitmap(boolean filter) {
+ super.setFilterBitmap(filter);
+
+ if (mDrawable != null) {
+ mDrawable.setFilterBitmap(filter);
+ }
+ }
+
+
+ @Override
+ public void setAlpha(int alpha) {
+ if (mDrawable != null) {
+ mDrawable.setAlpha(alpha);
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ if (mDrawable != null) {
+ mDrawable.setColorFilter(cf);
+ }
+ }
+
+ @Override
+ public int getOpacity() {
+ if (mDrawable != null) {
+ return mDrawable.getOpacity();
+ } else {
+ return PixelFormat.UNKNOWN;
+ }
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ if (mDrawable != null) {
+ return mDrawable.getIntrinsicWidth();
+ } else {
+ return super.getIntrinsicWidth();
+ }
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ if (mDrawable != null) {
+ return mDrawable.getIntrinsicHeight();
+ } else {
+ return super.getIntrinsicHeight();
+ }
+ }
+}
diff --git a/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java b/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java
index 510a987ed..9220f68b0 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java
+++ b/app/src/main/java/com/hippo/ehviewer/client/GalleryDetailParser.java
@@ -22,15 +22,28 @@ import com.hippo.ehviewer.util.EhUtils;
import com.hippo.util.AssertUtils;
import com.hippo.util.Utils;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GalleryDetailParser {
+ private static final DateFormat WEB_COMMENT_DATA_FORMAT = new SimpleDateFormat("dd MMMMM yyyy, HH:mm z", Locale.US);
+ private static final DateFormat OUT_COMMENT_DATA_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US);
+
+ static {
+ WEB_COMMENT_DATA_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
+ OUT_COMMENT_DATA_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
private static final String OFFENSIVE_STRING =
"
(And if you choose to ignore this warning, you lose all rights to complain about it in the future.)
";
private static final String PINING_STRING =
@@ -209,6 +222,7 @@ public class GalleryDetailParser {
TagGroup tagGroup = new TagGroup();
tagGroup.groupName = sortOut(m.group(1));
parseTagGroup(tagGroup, m.group(0));
+ tags.add(tagGroup);
}
}
@@ -232,7 +246,15 @@ public class GalleryDetailParser {
m = p.matcher(body);
comments = new LinkedList<>();
while (m.find()) {
- comments.add(new Comment(sortOut(m.group(1)), sortOut(m.group(2)), m.group(3)));
+ String webDateString = sortOut(m.group(1));
+ Date date;
+ try {
+ date = WEB_COMMENT_DATA_FORMAT.parse(webDateString);
+ } catch (java.text.ParseException e) {
+ date = new Date(0l);
+ }
+ String outDateString = OUT_COMMENT_DATA_FORMAT.format(date);
+ comments.add(new Comment(outDateString, date, sortOut(m.group(2)), m.group(3)));
}
Collections.sort(comments, COMMENT_SORTER);
}
@@ -257,67 +279,10 @@ public class GalleryDetailParser {
}
static class CommentSort implements Comparator {
- private int compareNum(String n1, String n2, int median) {
- int re = 0;
- for (int i = 0; i < median; i++) {
- re *= 10;
- re += n1.charAt(i) - n2.charAt(i);
- }
- return re;
- }
-
- private int getMonth(String m) {
- if (m.equals("January"))
- return 1;
- else if (m.equals("February"))
- return 2;
- else if (m.equals("March"))
- return 3;
- else if (m.equals("April"))
- return 4;
- else if (m.equals("May"))
- return 5;
- else if (m.equals("June"))
- return 6;
- else if (m.equals("July"))
- return 7;
- else if (m.equals("August"))
- return 8;
- else if (m.equals("September"))
- return 9;
- else if (m.equals("October"))
- return 10;
- else if (m.equals("November"))
- return 11;
- else if (m.equals("December"))
- return 12;
- else
- return 0;
- }
@Override
public int compare(Comment c1, Comment c2) {
- int re = 0;
- Pattern p = Pattern.compile("(\\d{2}) (\\w+) (\\d{4}), (\\d{2}):(\\d{2})");
- Matcher m1 = p.matcher(c1.time);
- Matcher m2 = p.matcher(c2.time);
- if (!m1.find() || !m2.find())
- return 0;
- // year
- re += compareNum(m1.group(3), m2.group(3), 4);
- re <<= 4;
- // Month
- re += getMonth(m1.group(2)) - getMonth(m2.group(2));
- re <<= 5;
- // Day
- re += compareNum(m1.group(1), m2.group(1), 2);
- re <<= 5;
- // Hour
- re += compareNum(m1.group(4), m2.group(4), 2);
- re <<= 6;
- // Minute
- re += compareNum(m1.group(5), m2.group(5), 2);
- return re;
+ return c1.date.compareTo(c2.date);
}
}
}
diff --git a/app/src/main/java/com/hippo/ehviewer/data/Comment.java b/app/src/main/java/com/hippo/ehviewer/data/Comment.java
index 2ebc37243..5de74a7f7 100644
--- a/app/src/main/java/com/hippo/ehviewer/data/Comment.java
+++ b/app/src/main/java/com/hippo/ehviewer/data/Comment.java
@@ -16,13 +16,17 @@
package com.hippo.ehviewer.data;
+import java.util.Date;
+
public class Comment {
public String time;
+ public Date date;
public String user;
public String comment;
- public Comment (String time, String user, String comment) {
+ public Comment (String time, Date date, String user, String comment) {
this.time = time;
+ this.date = date;
this.user = user;
this.comment = comment;
}
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 66e7d6129..6621c72f8 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
@@ -16,14 +16,19 @@
package com.hippo.ehviewer.ui.scene;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
+import android.text.Html;
import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.ViewSwitcher;
@@ -34,21 +39,31 @@ 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.util.Config;
import com.hippo.ehviewer.util.EhUtils;
import com.hippo.ehviewer.widget.LoadImageView;
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.Scene;
import com.hippo.util.Log;
+import com.hippo.util.URLImageGetter;
+import com.hippo.util.UiUtils;
+import com.hippo.widget.AutoWrapLayout;
+
+import java.util.List;
public class GalleryDetailScene extends Scene implements View.OnClickListener {
public static final String KEY_GALLERY_INFO = "gallery_info";
+ private FrameLayout mDetailFrameLayout;
+
private ViewGroup mHeader;
LoadImageView mThumb;
TextView mTitle;
@@ -72,11 +87,16 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener {
private TextView mTorrent;
private TextView mShare;
private TextView mRate;
+ private LinearLayout mTag;
+ private LinearLayout mComment;
+ private TextView mCommentMore;
private View mProgressBar;
private ViewTransition mViewTransition;
+ private GalleryDetail mGalleryDetail;
+
private void handleAnnouncer(Announcer announcer) {
GalleryInfo gi;
@@ -100,13 +120,7 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener {
Context context = getStageActivity();
Resources resources = context.getResources();
- mViewTransition.showView(1);
-
- RippleSalon.addRipple(mRead, false);
- RippleSalon.addRipple(mDownload, false);
-
- mRead.setOnClickListener(this);
- mDownload.setOnClickListener(this);
+ mViewTransition.showView(1, false);
mRatingText.setFactory(new RatingTextViewFactory());
@@ -125,6 +139,20 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener {
mTorrent.setCompoundDrawables(null, torrentDrawable, null, null);
mShare.setCompoundDrawables(null, shareDrawable, null, null);
mRate.setCompoundDrawables(null, rateDrawable, null, null);
+
+ mRead.setOnClickListener(this);
+ mDownload.setOnClickListener(this);
+ mFavorite.setOnClickListener(this);
+ mTorrent.setOnClickListener(this);
+ mShare.setOnClickListener(this);
+ mRate.setOnClickListener(this);
+
+ RippleSalon.addRipple(mRead, false);
+ RippleSalon.addRipple(mDownload, false);
+ RippleSalon.addRipple(mFavorite, false);
+ RippleSalon.addRipple(mTorrent, false);
+ RippleSalon.addRipple(mShare, false);
+ RippleSalon.addRipple(mRate, false);
}
@Override
@@ -132,6 +160,8 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener {
super.onCreate(savedInstanceState);
setContentView(R.layout.scene_gallery_detail);
+ mDetailFrameLayout = (FrameLayout) findViewById(R.id.detail_frame_layout);
+
mHeader = (ViewGroup) findViewById(R.id.header);
mThumb = (LoadImageView) mHeader.findViewById(R.id.thumb);
mTitle = (TextView) mHeader.findViewById(R.id.title);
@@ -155,6 +185,9 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener {
mTorrent = (TextView) mContent.findViewById(R.id.torrent);
mShare = (TextView) mContent.findViewById(R.id.share);
mRate = (TextView) mContent.findViewById(R.id.rate);
+ mTag = (LinearLayout) mContent.findViewById(R.id.tag);
+ mComment = (LinearLayout) mContent.findViewById(R.id.comment);
+ mCommentMore = (TextView) mContent.findViewById(R.id.comment_more);
mProgressBar = findViewById(R.id.progress_bar);
@@ -163,18 +196,35 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener {
handleAnnouncer(getAnnouncer());
}
+ @Override
+ protected void onGetFitPaddingBottom(int b) {
+ mDetailFrameLayout.setPadding(0, 0, 0, b);
+ }
+
@Override
public void onClick(View v) {
+ GalleryDetail gd = mGalleryDetail;
+ if (gd == null) {
+ return;
+ }
+
if (v == mRead) {
} else if (v == mDownload) {
+ } else if (v == mFavorite) {
+
+ } else if (v == mTorrent) {
+
+ } else if (v == mShare) {
+ StartActivityHelper.share(getStageActivity(),
+ EhClient.getDetailUrl(Config.getEhSource(), gd.gid, gd.token, 0));
}
}
private String getRatingText(float rating) {
String undefine = "(´_ゝ`)";
- if (rating == Float.NaN) {
+ if (Float.isNaN(rating)) {
return undefine;
}
@@ -216,6 +266,72 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener {
" (" + rating + ", " + ratedTimes + ")";
}
+ private void fillTags(List tags) {
+ Context context = getStageActivity();
+ LayoutInflater inflater = getStageActivity().getLayoutInflater();
+ int x = UiUtils.dp2pix(context, 2);
+ int y = UiUtils.dp2pix(context, 4);
+
+ for (TagGroup tagGroup : tags) {
+ LinearLayout tagGroupLayout = new LinearLayout(context);
+ tagGroupLayout.setOrientation(LinearLayout.HORIZONTAL);
+ AutoWrapLayout tagLayout = new AutoWrapLayout(context);
+
+ // 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);
+
+ 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
+ public void onClick(View v) {
+ Intent intent = new Intent(GalleryDetailActivity.this,
+ GalleryListActivity.class);
+ intent.setAction(GalleryListActivity.ACTION_GALLERY_LIST);
+ intent.putExtra(GalleryListActivity.KEY_MODE,
+ ListUrls.MODE_TAG);
+ intent.putExtra(GalleryListActivity.KEY_TAG, groupName + ":" + tag);
+ intent.setAction(GalleryListActivity.ACTION_GALLERY_LIST);
+ startActivity(intent);
+ }
+ });
+ */
+ AutoWrapLayout.LayoutParams alp = new AutoWrapLayout.LayoutParams();
+ alp.setMargins(x, y, x, y);
+ tagLayout.addView(tagView, alp);
+ }
+ tagGroupLayout.addView(tagLayout);
+ mTag.addView(tagGroupLayout);
+ }
+ }
+
+ private View createCommentView(Comment comment) {
+ 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);
+ commentText.setMaxLines(3);
+ commentText.setAutoLinkMask(0);
+
+
+ URLImageGetter p = new URLImageGetter(
+ commentText, EhApplication.getConaco(getStageActivity()));
+ commentText.setText(Html.fromHtml(comment.comment, p, null));
+
+ return view;
+ }
+
private class RatingTextViewFactory implements ViewSwitcher.ViewFactory {
@Override
public View makeView() {
@@ -229,6 +345,8 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener {
@Override
public void onSuccess(GalleryDetail galleryDetail) {
+ mGalleryDetail = galleryDetail;
+
Resources resources = getStageActivity().getResources();
mLanguage.setText(galleryDetail.language);
mPages.setText(String.format(resources.getString(R.string.page_count),
@@ -240,12 +358,30 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener {
galleryDetail.favoredTimes));
mRating.setRating(galleryDetail.rating);
mRatingText.setText(getAllRatingText(galleryDetail.rating, galleryDetail.ratedTimes));
+ fillTags(galleryDetail.tags);
+
+ int commentNum = galleryDetail.comments.size();
+ if (commentNum == 0) {
+ mCommentMore.setText("暂无评论");
+ } else if (commentNum <= 2){
+ mCommentMore.setText("暂无更多评论");
+ } else {
+ mCommentMore.setText("更多评论");
+ }
+ int maxShown = Math.min(2, commentNum);
+ for (int i = 0; i < maxShown; i++) {
+ mComment.addView(createCommentView(galleryDetail.comments.get(i)), i,
+ new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+ }
+
mViewTransition.showView(0);
}
@Override
public void onFailure(Exception e) {
e.printStackTrace();
+ // TODO
}
}
}
diff --git a/app/src/main/java/com/hippo/ehviewer/widget/LinkifyTextView.java b/app/src/main/java/com/hippo/ehviewer/widget/LinkifyTextView.java
new file mode 100644
index 000000000..186b8a76d
--- /dev/null
+++ b/app/src/main/java/com/hippo/ehviewer/widget/LinkifyTextView.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 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.annotation.SuppressLint;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.text.Layout;
+import android.text.Spannable;
+import android.text.style.ClickableSpan;
+import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+public class LinkifyTextView extends TextView {
+
+ private String mUrl;
+
+ public LinkifyTextView(Context context) {
+ super(context);
+ init();
+ }
+
+ public LinkifyTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public LinkifyTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ public void init() {
+ // Just make urls striking
+ // LinkifyTextView itself should not handle touch url event
+ setAutoLinkMask(Linkify.WEB_URLS);
+ setLinksClickable(false);
+ }
+
+ public String getTouchedUrl() {
+ return mUrl;
+ }
+
+ /**
+ * Call it when you do not need the url any more
+ */
+ public void clearTouchedUrl() {
+ mUrl = null;
+ }
+
+ @Override
+ @SuppressLint("ClickableViewAccessibility")
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ if (getText() instanceof Spannable) {
+ // Get this code from android.text.method.LinkMovementMethod.
+ // Work fine !
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ boolean isGetUrl = false;
+
+ x -= getTotalPaddingLeft();
+ y -= getTotalPaddingTop();
+
+ x += getScrollX();
+ y += getScrollY();
+
+ Layout layout = getLayout();
+ int line = layout.getLineForVertical(y);
+ int off = layout.getOffsetForHorizontal(line, x);
+
+ ClickableSpan[] links = ((Spannable)getText()).getSpans(off, off, ClickableSpan.class);
+
+ for (ClickableSpan link : links) {
+ if (link instanceof URLSpan) {
+ mUrl = ((URLSpan)link).getURL();
+ isGetUrl = true;
+ break;
+ }
+ }
+ if (!isGetUrl) {
+ mUrl = null;
+ }
+ } else {
+ mUrl = null;
+ }
+
+ return super.onTouchEvent(event);
+ }
+}
diff --git a/app/src/main/java/com/hippo/miscellaneous/StartActivityHelper.java b/app/src/main/java/com/hippo/miscellaneous/StartActivityHelper.java
new file mode 100644
index 000000000..373aee194
--- /dev/null
+++ b/app/src/main/java/com/hippo/miscellaneous/StartActivityHelper.java
@@ -0,0 +1,49 @@
+package com.hippo.miscellaneous;
+
+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 StartActivityHelper {
+
+ 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.em_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.em_cant_find_activity, Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ }
+}
diff --git a/app/src/main/java/com/hippo/util/URLImageGetter.java b/app/src/main/java/com/hippo/util/URLImageGetter.java
new file mode 100644
index 000000000..cfe74e8c4
--- /dev/null
+++ b/app/src/main/java/com/hippo/util/URLImageGetter.java
@@ -0,0 +1,26 @@
+package com.hippo.util;
+
+import android.graphics.drawable.Drawable;
+import android.text.Html;
+import android.widget.TextView;
+
+import com.hippo.conaco.Conaco;
+import com.hippo.drawable.UnikeryDrawable;
+
+public class URLImageGetter implements Html.ImageGetter {
+
+ private TextView mTextView;
+ private Conaco mConaco;
+
+ public URLImageGetter(TextView view, Conaco conaco) {
+ mTextView = view;
+ mConaco = conaco;
+ }
+
+ @Override
+ public Drawable getDrawable(String source) {
+ UnikeryDrawable drawable = new UnikeryDrawable(mTextView);
+ mConaco.load(drawable, source, source);
+ return drawable;
+ }
+}
diff --git a/app/src/main/java/com/hippo/widget/AutoWrapLayout.java b/app/src/main/java/com/hippo/widget/AutoWrapLayout.java
new file mode 100644
index 000000000..a4a1fa944
--- /dev/null
+++ b/app/src/main/java/com/hippo/widget/AutoWrapLayout.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2014 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.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.hippo.ehviewer.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A ViewGroup that can layout views in line and auto wrap
+ *
+ * @author Hippo
+ *
+ */
+public class AutoWrapLayout extends ViewGroup {
+
+ private final static String TAG = "AutoWrapLayout";
+
+ private final List rectList = new ArrayList<>();
+
+ private Alignment mAlignment;
+
+ private static final Alignment[] sBaseLineArray = { Alignment.TOP,
+ Alignment.CENTER, Alignment.BOTTOM };
+
+
+ public enum Alignment {
+ TOP(0),
+ CENTER(1),
+ BOTTOM(2);
+
+ Alignment(int ni) {
+ nativeInt = ni;
+ }
+
+ final int nativeInt;
+ }
+
+ public AutoWrapLayout(Context context) {
+ super(context);
+ }
+ public AutoWrapLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+ public AutoWrapLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.AutoWrapLayout, defStyle, 0);
+
+ int index = a.getInt(R.styleable.AutoWrapLayout_alignment, -1);
+ if (index >= 0) {
+ setAlignment(sBaseLineArray[index]);
+ }
+
+ a.recycle();
+ }
+
+ public void setAlignment(Alignment baseLine) {
+ if (baseLine == null) {
+ return;
+ }
+
+ if (mAlignment != baseLine) {
+ mAlignment = baseLine;
+
+ requestLayout();
+ invalidate();
+ }
+ }
+
+ public Alignment getAlignment() {
+ return mAlignment;
+ }
+
+ private void adjustBaseLine(int lineHeight, int startIndex, int endIndex) {
+ if (mAlignment == Alignment.TOP)
+ return;
+
+ for (int index = startIndex; index < endIndex; index++) {
+ final View child = getChildAt(index);
+ final LayoutParams lp =
+ (LayoutParams)child.getLayoutParams();
+ Rect rect = rectList.get(index);
+ int offsetRaw = lineHeight - rect.height() - lp.topMargin - lp.bottomMargin;
+ if (mAlignment == Alignment.CENTER)
+ rect.offset(0, offsetRaw/2);
+ else if (mAlignment == Alignment.BOTTOM)
+ rect.offset(0, offsetRaw);
+ }
+ }
+
+
+ // TODO Take vertical mode
+ /**
+ * each row or line at least show one child
+ *
+ * horizontal only show child can show or partly show in parent
+ */
+ @SuppressLint("DrawAllocation")
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ 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 = Integer.MAX_VALUE;
+ if (heightMode == MeasureSpec.UNSPECIFIED)
+ maxHeight = Integer.MAX_VALUE;
+
+ int paddingLeft = getPaddingLeft();
+ int paddingTop = getPaddingTop();
+ int paddingRight = getPaddingRight();
+ int paddingBottom = getPaddingBottom();
+
+ int maxRightBound = maxWidth - paddingRight;
+ int maxBottomBound = maxHeight - paddingBottom;
+
+ int left;
+ int top;
+ int right;
+ int bottom;
+ int rightBound = paddingLeft;
+ int maxRightNoPadding = rightBound;
+ int bottomBound;
+ int lastMaxBottom = paddingTop;
+ int maxBottom = lastMaxBottom;
+ int childWidth;
+ int childHeight;
+
+ int lineStartIndex = 0;
+ int lineEndIndex = 0; // endIndex + 1
+
+ rectList.clear();
+ int childCount = getChildCount();
+ for (int index = 0; index < childCount; index++) {
+ final View child = getChildAt(index);
+ child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ if (child.getVisibility() == View.GONE)
+ continue;
+ final LayoutParams lp =
+ (LayoutParams)child.getLayoutParams();
+ childWidth = child.getMeasuredWidth();
+ childHeight = child.getMeasuredHeight();
+
+ left = rightBound + lp.leftMargin;
+ right = left + childWidth;
+ rightBound = right + lp.rightMargin;
+ if (rightBound > maxRightBound) { // Go to next row
+ lineEndIndex = index;
+ // Adjust child position base on baseline
+ adjustBaseLine(maxBottom - lastMaxBottom, lineStartIndex, lineEndIndex);
+
+ // If child can't show in parent begin this line
+ if (maxBottom >= maxBottomBound)
+ break;
+
+ // If it is first item in line, try to show it all
+ if (lineEndIndex == lineStartIndex) {
+ child.measure(MeasureSpec.makeMeasureSpec(
+ maxWidth - paddingLeft - paddingRight
+ - lp.leftMargin - lp.rightMargin, MeasureSpec.AT_MOST),
+ MeasureSpec.UNSPECIFIED);
+ childWidth = child.getMeasuredWidth();
+ childHeight = child.getMeasuredHeight();
+ }
+ left = paddingLeft + lp.leftMargin;
+ right = left + childWidth;
+ rightBound = right + lp.rightMargin;
+
+ lastMaxBottom = maxBottom;
+ top = lastMaxBottom + lp.topMargin;
+ bottom = top + childHeight;
+ bottomBound = bottom + lp.bottomMargin;
+
+ lineStartIndex = index;
+ } else {
+ top = lastMaxBottom + lp.topMargin;
+ bottom = top + childHeight;
+ bottomBound = bottom + lp.bottomMargin;
+ }
+ // Update max
+ if (rightBound > maxRightNoPadding)
+ maxRightNoPadding = rightBound;
+ if (bottomBound > maxBottom)
+ maxBottom = bottomBound;
+ Rect rect = new Rect();
+ rect.left = left;
+ rect.top = top;
+ rect.right = right;
+ rect.bottom = bottom;
+ rectList.add(rect);
+ }
+
+ // Handle last line baseline
+ adjustBaseLine(maxBottom - lastMaxBottom, lineStartIndex, rectList.size());
+
+ int measuredWidth;
+ int measuredHeight;
+
+ if (widthMode == MeasureSpec.EXACTLY)
+ measuredWidth = maxWidth;
+ else
+ measuredWidth = maxRightNoPadding + paddingRight;
+ if (heightMode == MeasureSpec.EXACTLY)
+ measuredHeight = maxHeight;
+ else {
+ measuredHeight = maxBottom + paddingBottom;
+ if (heightMode == MeasureSpec.AT_MOST)
+ measuredHeight = measuredHeight > maxHeight ? maxHeight : measuredHeight;
+ }
+
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int count = rectList.size();
+ for(int i = 0; i < count; i++){
+ final View child = this.getChildAt(i);
+ if (child.getVisibility() == View.GONE)
+ continue;
+ Rect rect = rectList.get(i);
+ child.layout(rect.left, rect.top, rect.right, rect.bottom);
+ }
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new LayoutParams(p);
+ }
+
+ public static class LayoutParams extends MarginLayoutParams {
+ public LayoutParams() {
+ this(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+ }
+}
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 80cf7e544..d408b1898 100644
--- a/app/src/main/res/layout-v21/scene_gallery_detail.xml
+++ b/app/src/main/res/layout-v21/scene_gallery_detail.xml
@@ -26,6 +26,7 @@
android:layout_height="match_parent">
@@ -104,7 +105,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
- android:text="更多信息"
+ android:text="@string/more_information"
android:textColor="@color/theme_accent"/>
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_comment.xml b/app/src/main/res/layout/item_comment.xml
new file mode 100644
index 000000000..a7755f226
--- /dev/null
+++ b/app/src/main/res/layout/item_comment.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/tag.xml b/app/src/main/res/layout/tag.xml
new file mode 100644
index 000000000..75b8838b0
--- /dev/null
+++ b/app/src/main/res/layout/tag.xml
@@ -0,0 +1,26 @@
+
+
+
diff --git a/app/src/main/res/layout/tag_group.xml b/app/src/main/res/layout/tag_group.xml
new file mode 100644
index 000000000..8a3498c3b
--- /dev/null
+++ b/app/src/main/res/layout/tag_group.xml
@@ -0,0 +1,26 @@
+
+
+
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index ee3924880..476bd5882 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -63,4 +63,12 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a575cfd44..95f95c2d3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -80,10 +80,11 @@
%d pages
%d \u2665
- We feel %s
+ More information
+ It is %s
- MASTERPIECE
+ a MASTERPIECE
AMAZING
GREAT
GOOD
@@ -104,6 +105,8 @@
Weird
Network available ?
+ Can\'t find target Activity
+
Display