/** * Note represents a single WordPress.com notification */ package org.wordpress.android.models; import android.text.Html; import android.text.Spanned; import android.util.Log; import android.text.style.QuoteSpan; import android.text.SpannableStringBuilder; import java.util.Map; import java.util.HashMap; import org.json.JSONObject; import org.json.JSONArray; import org.json.JSONException; import org.wordpress.android.util.JSONUtil; import org.wordpress.android.util.Emoticons; import org.wordpress.android.util.WPHtml; import org.wordpress.android.util.WPHtmlTagHandler; public class Note { protected static final String TAG = "NoteModel"; public static final String UNKNOWN_TYPE = "unknown"; public static final String COMMENT_TYPE = "comment"; public static final String LIKE_TYPE = "like"; // Notes have different types of "templates" for displaying differently // this is not a canonical list but covers all the types currently in use public static final String SINGLE_LINE_LIST_TEMPLATE = "single-line-list"; public static final String MULTI_LINE_LIST_TEMPLATE = "multi-line-list"; public static final String BIG_BADGE_TEMPLATE = "big-badge"; // JSON keys and values for looking up values private static final String NOTE_ACTION_REPLY = "replyto-comment"; private static final String REPLY_CONTENT_PARAM_KEY = "content"; private Map<String, JSONObject> mActions; private Reply mReply; private JSONObject mNoteJSON; private SpannableStringBuilder mComment = new SpannableStringBuilder(); /** * Create a note using JSON from REST API */ public Note(JSONObject noteJSON) { mNoteJSON = noteJSON; // get the comment ready if it's a comment type cleanupComment(); } public String toString() { return getSubject(); } public JSONObject toJSONObject() { return mNoteJSON; } public String getId() { return queryJSON("id", "0"); } public String getType() { return queryJSON("type", UNKNOWN_TYPE); } public Boolean isType(String type) { return getType().equals(type); } public Boolean isCommentType() { return isType(COMMENT_TYPE); } public String getSubject() { String text = queryJSON("subject.text", "").trim(); if (text.equals("")) { text = queryJSON("subject.html", ""); } return Html.fromHtml(text).toString(); } public String getIconURL() { return queryJSON("subject.icon", ""); } /** * Removes HTML and cleans up newlines and whitespace */ public String getCommentPreview() { return getCommentBody().toString().replaceAll("\uFFFC", "") .replace("\n", " ").replaceAll("[\\s]{2,}", " ").trim(); } /** * Gets the comment's text with getCommentText() and sends it through * HTML.fromHTML */ public Spanned getCommentBody() { return mComment; } /** * For a comment note the text is in the body object's last item. It * currently is only provided in HTML format. */ public String getCommentText() { return queryJSON("body.items[last].html", ""); } /** * The inverse of isRead */ public Boolean isUnread() { return !isRead(); } /** * A note can have an "unread" of 0 or more ("likes" can have unread of 2+) * to indicate the quantity of likes that are "unread" within the single * note. So for a note to be "read" it should have "0" */ public Boolean isRead() { return getUnreadCount().equals("0"); } /** * For some reason the unread count is a string in the JSON API but is truly * representd by an Integer. We can handle a simple string. */ public String getUnreadCount() { return queryJSON("unread", "0"); } /** * */ public void setUnreadCount(String count) { try { mNoteJSON.putOpt("unread", count); } catch (JSONException e) { Log.e(TAG, "Failed to set unread property", e); } } public Reply buildReply(String content) { JSONObject replyAction = getActions().get(NOTE_ACTION_REPLY); String restPath = JSONUtil.queryJSON(replyAction, "params.rest_path", ""); Log.d(TAG, String.format("Search actions %s", restPath)); Reply reply = new Reply(this, String.format("%s/replies/new", restPath), content); return reply; } /** * Get the timestamp provided by the API for the note. */ public String getTimestamp() { return queryJSON("timestamp", ""); } public String getTemplate() { return queryJSON("body.template", ""); } public Boolean isMultiLineListTemplate() { return getTemplate().equals(MULTI_LINE_LIST_TEMPLATE); } public Boolean isSingleLineListTemplate() { return getTemplate().equals(SINGLE_LINE_LIST_TEMPLATE); } public Boolean isBigBadgeTemplate() { return getTemplate().equals(BIG_BADGE_TEMPLATE); } public Map<String, JSONObject> getActions() { if (mActions == null) { try { JSONArray actions = queryJSON("body.actions", new JSONArray()); mActions = new HashMap<String, JSONObject>(actions.length()); for (int i = 0; i < actions.length(); i++) { JSONObject action = actions.getJSONObject(i); String actionType = JSONUtil.queryJSON(action, "type", ""); if (!actionType.equals("")) { mActions.put(actionType, action); } } } catch (JSONException e) { Log.e(TAG, "Could not find actions", e); mActions = new HashMap<String, JSONObject>(); } } return mActions; } /** * Prepares the comment HTML for being displayed. Cleans up emoticons. * * TODO: Caching comment images */ protected void cleanupComment() { if (isCommentType()) { mComment = Note.prepareHtml(getCommentText()); } } protected Object queryJSON(String query) { Object defaultObject = ""; return JSONUtil.queryJSON(this.toJSONObject(), query, defaultObject); } /** * Rudimentary system for pulling an item out of a JSON object hierarchy */ public <U> U queryJSON(String query, U defaultObject) { return JSONUtil.queryJSON(this.toJSONObject(), query, defaultObject); } /** * Represents a user replying to a note. Holds */ public static class Reply { private Note mNote; private String mContent; private String mRestPath; private JSONObject mCommentJson; Reply(Note note, String restPath, String content) { mNote = note; mRestPath = restPath; mContent = content; } public String getContent() { return mContent; } public Note getNote() { return mNote; } public String getUrl() { if (isComplete()) { return JSONUtil.queryJSON(mCommentJson, "URL", ""); } return null; } public String getAvatarUrl() { if (isComplete()) { return JSONUtil .queryJSON(mCommentJson, "author.avatar_URL", ""); } else { return ""; } } /** * Passes through Html.fromHtml to remove markup and replaces smilies * with emoji */ public String getCommentPreview() { if (isComplete()) { String text = JSONUtil.queryJSON(mCommentJson, "content", ""); SpannableStringBuilder html = (SpannableStringBuilder) Html .fromHtml(text); return Emoticons.replaceEmoticonsWithEmoji(html).toString() .trim(); } else { return ""; } } public String getRestPath() { return mRestPath; } public boolean isComplete() { return mCommentJson != null; } public JSONObject getCommentJson() { return mCommentJson; } public void setCommentJson(JSONObject commentJson) { mCommentJson = commentJson; } } /** * Replaces emoticons with emoji */ public static SpannableStringBuilder prepareHtml(String text) { SpannableStringBuilder html = (SpannableStringBuilder) Html.fromHtml( text, null, new WPHtmlTagHandler()); Emoticons.replaceEmoticonsWithEmoji(html); QuoteSpan spans[] = html.getSpans(0, html.length(), QuoteSpan.class); for (QuoteSpan span : spans) { html.setSpan(new WPHtml.WPQuoteSpan(), html.getSpanStart(span), html.getSpanEnd(span), html.getSpanFlags(span)); html.removeSpan(span); } return html; } }