From 0aad8ca1e8378099a76afa04a48e28a7cd2f478e Mon Sep 17 00:00:00 2001 From: seven332 Date: Mon, 28 Mar 2016 02:04:05 +0800 Subject: [PATCH] Add vote comment --- .../com/hippo/ehviewer/client/EhClient.java | 3 + .../com/hippo/ehviewer/client/EhEngine.java | 37 ++++ .../ehviewer/client/data/GalleryComment.java | 15 ++ .../client/parser/GalleryDetailParser.java | 25 +++ .../client/parser/VoteCommentParser.java | 41 ++++ .../ui/scene/GalleryCommentsScene.java | 181 ++++++++++++++++-- app/src/main/java/com/hippo/text/Html.java | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 10 + app/src/main/res/values-zh-rHK/strings.xml | 10 + app/src/main/res/values-zh-rTW/strings.xml | 10 + app/src/main/res/values/strings.xml | 10 + 11 files changed, 331 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/hippo/ehviewer/client/parser/VoteCommentParser.java 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 5957b2cd7..e17a2e313 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -56,6 +56,7 @@ public class EhClient { public static final int METHOD_GET_TORRENT_LIST = 12; public static final int METHOD_GET_WHATS_HOT = 13; public static final int METHOD_GET_PROFILE = 14; + public static final int METHOD_VOTE_COMMENT = 15; private final ThreadPoolExecutor mRequestThreadPool; private final OkHttpClient mOkHttpClient; @@ -177,6 +178,8 @@ public class EhClient { return EhEngine.getWhatsHot(this, mOkHttpClient); case METHOD_GET_PROFILE: return EhEngine.getProfile(this, mOkHttpClient); + case METHOD_VOTE_COMMENT: + return EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (Integer) params[3]); default: return new IllegalStateException("Can't detect method " + mMethod); } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java index 52a918c62..378d97550 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -41,6 +41,7 @@ import com.hippo.ehviewer.client.parser.ProfileParser; import com.hippo.ehviewer.client.parser.RateGalleryParser; import com.hippo.ehviewer.client.parser.SignInParser; import com.hippo.ehviewer.client.parser.TorrentParser; +import com.hippo.ehviewer.client.parser.VoteCommentParser; import com.hippo.ehviewer.client.parser.WhatsHotParser; import com.hippo.network.StatusCodeException; import com.hippo.util.ReadableTime; @@ -672,4 +673,40 @@ public class EhEngine { throw e; } } + + public static VoteCommentParser.Result voteComment(EhClient.Task task, OkHttpClient okHttpClient, + long gid, String token, long commentId, int commentVote) throws Exception { + final JSONObject json = new JSONObject(); + json.put("method", "votecomment"); + json.put("apiuid", APIUID); + json.put("apikey", APIKEY); + json.put("gid", gid); + json.put("token", token); + json.put("comment_id", commentId); + json.put("comment_vote", commentVote); + final RequestBody requestBody = RequestBody.create(JSON, json.toString()); + String url = EhUrl.getApiUrl(); + Log.d(TAG, url); + Request request = new EhRequestBuilder(url, task.getEhConfig()) + .post(requestBody) + .build(); + Call call = okHttpClient.newCall(request); + + // Put call + task.setCall(call); + + String body = null; + Headers headers = null; + int code = -1; + try { + Response response = call.execute(); + code = response.code(); + headers = response.headers(); + body = response.body().string(); + return VoteCommentParser.parse(body, commentVote); + } catch (Exception e) { + throwException(call, code, headers, body, e); + throw e; + } + } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryComment.java b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryComment.java index 431c98722..141d89378 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryComment.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryComment.java @@ -21,6 +21,11 @@ import android.os.Parcelable; public class GalleryComment implements Parcelable { + // 0 for uploader comment. can't vote + public long id; + public int score; + public boolean voteUp; + public boolean voteDown; public long time; public String user; public String comment; @@ -32,6 +37,10 @@ public class GalleryComment implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeInt(this.score); + dest.writeByte(voteUp ? (byte) 1 : (byte) 0); + dest.writeByte(voteDown ? (byte) 1 : (byte) 0); dest.writeLong(this.time); dest.writeString(this.user); dest.writeString(this.comment); @@ -41,16 +50,22 @@ public class GalleryComment implements Parcelable { } protected GalleryComment(Parcel in) { + this.id = in.readLong(); + this.score = in.readInt(); + this.voteUp = in.readByte() != 0; + this.voteDown = in.readByte() != 0; this.time = in.readLong(); this.user = in.readString(); this.comment = in.readString(); } public static final Creator CREATOR = new Creator() { + @Override public GalleryComment createFromParcel(Parcel source) { return new GalleryComment(source); } + @Override public GalleryComment[] newArray(int size) { return new GalleryComment[size]; } diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.java b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.java index 6d1b9fab9..533c9a242 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.java +++ b/app/src/main/java/com/hippo/ehviewer/client/parser/GalleryDetailParser.java @@ -18,6 +18,7 @@ package com.hippo.ehviewer.client.parser; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.text.TextUtils; import com.hippo.ehviewer.client.EhUtils; import com.hippo.ehviewer.client.data.GalleryComment; @@ -342,11 +343,35 @@ public class GalleryDetailParser { public static GalleryComment parseComment(Element element) { try { GalleryComment comment = new GalleryComment(); + // Id + Element a = element.previousElementSibling(); + String name = a.attr("name"); + comment.id = Integer.parseInt(StringUtils.trim(name).substring(1)); + // Vote up and vote down + Element c4 = JsoupUtils.getElementByClass(element, "c4"); + if (null != c4) { + Elements es = c4.children(); + if (2 == es.size()) { + comment.voteUp = !TextUtils.isEmpty(StringUtils.trim(es.get(0).attr("style"))); + comment.voteDown = !TextUtils.isEmpty(StringUtils.trim(es.get(1).attr("style"))); + } + } + // Score + Element c5 = JsoupUtils.getElementByClass(element, "c5"); + if (null != c5) { + Elements es = c5.children(); + if (!es.isEmpty()) { + comment.score = NumberUtils.parseIntSafely(StringUtils.trim(es.get(0).text()), 0); + } + } + // time Element c3 = JsoupUtils.getElementByClass(element, "c3"); String temp = c3.ownText(); temp = temp.substring("Posted on ".length(), temp.length() - " by:  ".length()); comment.time = WEB_COMMENT_DATE_FORMAT.parse(temp).getTime(); + // user comment.user = c3.child(0).text(); + // comment comment.comment = JsoupUtils.getElementByClass(element, "c6").html(); return comment; } catch (Exception e) { diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/VoteCommentParser.java b/app/src/main/java/com/hippo/ehviewer/client/parser/VoteCommentParser.java new file mode 100644 index 000000000..acf7aa2d7 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/parser/VoteCommentParser.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.client.parser; + +import org.json.JSONException; +import org.json.JSONObject; + +public class VoteCommentParser { + + public static class Result { + public long id; + public int score; + public int vote; + public int expectVote; + } + + // {"comment_id":1253922,"comment_score":-19,"comment_vote":0} + public static Result parse(String body, int vote) throws JSONException { + Result result = new Result(); + JSONObject jo = new JSONObject(body); + result.id = jo.getLong("comment_id"); + result.score = jo.getInt("comment_score"); + result.vote = jo.getInt("comment_vote"); + result.expectVote = vote; + return result; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java index f81ec7241..89b5e009b 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java @@ -18,19 +18,32 @@ package com.hippo.ehviewer.ui.scene; import android.animation.Animator; import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; +import android.content.DialogInterface; import android.content.res.Resources; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StyleSpan; import android.text.style.URLSpan; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -38,7 +51,6 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; -import com.hippo.reveal.ViewAnimationUtils; import com.hippo.easyrecyclerview.EasyRecyclerView; import com.hippo.easyrecyclerview.LinearDividerItemDecoration; import com.hippo.ehviewer.EhApplication; @@ -48,7 +60,9 @@ import com.hippo.ehviewer.client.EhClient; import com.hippo.ehviewer.client.EhRequest; import com.hippo.ehviewer.client.EhUrl; import com.hippo.ehviewer.client.data.GalleryComment; +import com.hippo.ehviewer.client.parser.VoteCommentParser; import com.hippo.ehviewer.ui.MainActivity; +import com.hippo.reveal.ViewAnimationUtils; import com.hippo.rippleold.RippleSalon; import com.hippo.scene.SceneFragment; import com.hippo.text.Html; @@ -66,7 +80,9 @@ import com.hippo.yorozuya.ViewUtils; public final class GalleryCommentsScene extends ToolbarScene implements EasyRecyclerView.OnItemClickListener, - EasyRecyclerView.OnItemLongClickListener, View.OnClickListener { + View.OnClickListener { + + public static final String TAG = GalleryCommentsScene.class.getSimpleName(); public static final String KEY_GID = "gid"; public static final String KEY_TOKEN = "token"; @@ -172,9 +188,13 @@ public final class GalleryCommentsScene extends ToolbarScene mRecyclerView.setSelector(RippleSalon.generateRippleDrawable(false)); mRecyclerView.setHasFixedSize(true); mRecyclerView.setOnItemClickListener(this); - mRecyclerView.setOnItemLongClickListener(this); mRecyclerView.setPadding(mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(), mRecyclerView.getPaddingRight(), mRecyclerView.getPaddingBottom() + paddingBottomFab); + // Cancel change animator + RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator(); + if (itemAnimator instanceof DefaultItemAnimator) { + ((DefaultItemAnimator) itemAnimator).setSupportsChangeAnimations(false); + } mSendImage.setOnClickListener(this); mFab.setOnClickListener(this); @@ -215,6 +235,62 @@ public final class GalleryCommentsScene extends ToolbarScene onBackPressed(); } + private void voteComment(long id, int vote) { + Context context = getContext2(); + MainActivity activity = getActivity2(); + if (null == context || null == activity) { + return; + } + + EhRequest request = new EhRequest() + .setMethod(EhClient.METHOD_VOTE_COMMENT) + .setArgs(mGid, mToken, id, vote) + .setCallback(new VoteCommentListener(context, + activity.getStageId(), getTag())); + EhApplication.getEhClient(context).execute(request); + } + + private void showCommentDialog(final int position) { + final Context context = getContext2(); + if (null == context || null == mComments || position >= mComments.length || position < 0) { + return; + } + + final GalleryComment comment = mComments[position]; + String[] menuArray; + Resources resources = context.getResources(); + if (0 == comment.id) { + // 0 id is uploader comment, can't vote + menuArray = new String[1]; + menuArray[0] = resources.getString(R.string.copy_comment_text); + } else { + menuArray = new String[3]; + menuArray[0] = resources.getString(R.string.copy_comment_text); + menuArray[1] = resources.getString(comment.voteUp ? R.string.cancel_vote_up : R.string.vote_up); + menuArray[2] = resources.getString(comment.voteDown ? R.string.cancel_vote_down : R.string.vote_down); + } + + new AlertDialog.Builder(context) + .setItems(menuArray, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: // Copy + ClipboardManager cmb = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + cmb.setPrimaryClip(ClipData.newPlainText(null, comment.comment)); + showTip(R.string.copied_to_clipboard, LENGTH_SHORT); + break; + case 1: // Vote up + voteComment(comment.id, 1); + break; + case 2: // Vote down + voteComment(comment.id, -1); + break; + } + } + }).show(); + } + @Override public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) { Activity activity = getActivity2(); @@ -233,12 +309,10 @@ public final class GalleryCommentsScene extends ToolbarScene return true; } } - return false; - } - @Override - public boolean onItemLongClick(EasyRecyclerView parent, View view, int position, long id) { - return false; + showCommentDialog(position); + + return true; } private void updateView(boolean animation) { @@ -406,9 +480,9 @@ public final class GalleryCommentsScene extends ToolbarScene private class CommentHolder extends RecyclerView.ViewHolder { - public TextView user; - public TextView time; - public LinkifyTextView comment; + public final TextView user; + public final TextView time; + public final LinkifyTextView comment; public CommentHolder(View itemView) { super(itemView); @@ -432,6 +506,24 @@ public final class GalleryCommentsScene extends ToolbarScene return new CommentHolder(mInflater.inflate(R.layout.item_gallery_comment, parent, false)); } + public CharSequence generateComment(Context context, TextView textView, GalleryComment comment) { + SpannableStringBuilder ssb = Html.fromHtml(comment.comment, new URLImageGetter(textView, + EhApplication.getConaco(context)), null); + + if (0 != comment.id && 0 != comment.score) { + int score = comment.score; + String scoreString = score > 0 ? "+" + score : Integer.toString(score); + SpannableString ss = new SpannableString(scoreString); + ss.setSpan(new RelativeSizeSpan(0.8f), 0, scoreString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ss.setSpan(new StyleSpan(Typeface.BOLD), 0, scoreString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ss.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.secondary_text_default_light)) + , 0, scoreString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(" ").append(ss); + } + + return TextUrl.handleTextUrl(ssb); + } + @Override public void onBindViewHolder(CommentHolder holder, int position) { Context context = getContext2(); @@ -442,8 +534,7 @@ public final class GalleryCommentsScene extends ToolbarScene GalleryComment comment = mComments[position]; holder.user.setText(comment.user); holder.time.setText(ReadableTime.getTimeAgo(comment.time)); - holder.comment.setText(TextUrl.handleTextUrl(Html.fromHtml(comment.comment, - new URLImageGetter(holder.comment, EhApplication.getConaco(context)), null))); + holder.comment.setText(generateComment(context, holder.comment, comment)); } @Override @@ -471,6 +562,37 @@ public final class GalleryCommentsScene extends ToolbarScene updateView(true); } + private void onVoteCommentSuccess(VoteCommentParser.Result result) { + if (null == mAdapter || null == mComments) { + return; + } + + int position = -1; + for (int i = 0, n = mComments.length; i < n; i++) { + GalleryComment comment = mComments[i]; + if (comment.id == result.id) { + position = i; + break; + } + } + + if (-1 == position) { + Log.d(TAG, "Can't find comment with id " + result.id); + return; + } + + // Update comment + GalleryComment comment = mComments[position]; + comment.score = result.score; + if (result.expectVote > 0) { + comment.voteUp = 0 != result.vote; + } else { + comment.voteDown = 0 != result.vote; + } + + mAdapter.notifyItemChanged(position); + } + private static class CommentGalleryListener extends EhCallback { public CommentGalleryListener(Context context, int stageId, String sceneTag) { @@ -501,4 +623,37 @@ public final class GalleryCommentsScene extends ToolbarScene return scene instanceof GalleryCommentsScene; } } + + private static class VoteCommentListener extends EhCallback { + + public VoteCommentListener(Context context, int stageId, String sceneTag) { + super(context, stageId, sceneTag); + } + + @Override + public void onSuccess(VoteCommentParser.Result result) { + showTip(result.expectVote > 0 ? + (0 != result.vote ? R.string.vote_up_successfully : R.string.cancel_vote_up_successfully) : + (0 != result.vote ? R.string.vote_down_successfully : R.string.cancel_vote_down_successfully), + LENGTH_SHORT); + + GalleryCommentsScene scene = getScene(); + if (scene != null) { + scene.onVoteCommentSuccess(result); + } + } + + @Override + public void onFailure(Exception e) { + showTip(R.string.vote_failed, LENGTH_SHORT); + } + + @Override + public void onCancel() {} + + @Override + public boolean isInstance(SceneFragment scene) { + return scene instanceof GalleryCommentsScene; + } + } } diff --git a/app/src/main/java/com/hippo/text/Html.java b/app/src/main/java/com/hippo/text/Html.java index aabc9a0f3..4e69e7342 100644 --- a/app/src/main/java/com/hippo/text/Html.java +++ b/app/src/main/java/com/hippo/text/Html.java @@ -136,7 +136,7 @@ public class Html { * *

This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. */ - public static Spanned fromHtml(String source, ImageGetter imageGetter, + public static SpannableStringBuilder fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler) { Parser parser = new Parser(); try { @@ -462,7 +462,7 @@ class HtmlToSpannedConverter implements ContentHandler { mReader = parser; } - public Spanned convert() { + public SpannableStringBuilder convert() { mReader.setContentHandler(this); try { diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 621c0b23a..48c017c8d 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -237,6 +237,16 @@ 暂时没有评论 评论成功 评论失败 + 复制评论文字 + 深表赞同 + 不再深表赞同 + 垃圾评论 + 不是垃圾评论 + 已深表赞同 + 已不再深表赞同 + 已钦定为垃圾评论 + 已不再钦定为垃圾评论 + 投票失败 画廊预览 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index ef9c8fb85..c4a93e1f5 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -237,6 +237,16 @@ 暫時沒有評論 評論成功 評論失敗 + 複製評論文字 + 深表贊同 + 不再深表贊同 + 垃圾評論 + 不是垃圾評論 + 已深表贊同 + 已不再深表贊同 + 已欽定為垃圾評論 + 已不再欽定為垃圾評論 + 投票失敗 畫廊預覽 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 1e80050da..1ebaf73e5 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -237,6 +237,16 @@ 暫時沒有評論 評論成功 評論失敗 + 複製評論文字 + 深表贊同 + 不再深表贊同 + 垃圾評論 + 不是垃圾評論 + 已深表贊同 + 已不再深表贊同 + 已欽定為垃圾評論 + 已不再欽定為垃圾評論 + 投票失敗 畫廊預覽 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2d59b1abb..b29f718d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -280,6 +280,16 @@ No one comments on this gallery Comment successfully Comment failed + Copy comment text + Vote up + Cancel up-vote + Vote down + Cancel down-vote + Vote up successfully + Cancel up-vote successfully + Vote down successfully + Cancel down-vote successfully + Vote failed Gallery Previews