diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml
index db1fa18ac21b..91f93516757a 100644
--- a/WordPress/src/main/AndroidManifest.xml
+++ b/WordPress/src/main/AndroidManifest.xml
@@ -83,7 +83,12 @@
+ android:name=".ui.comments.CommentsActivity"
+ android:theme="@style/CalypsoTheme" />
+
@@ -238,7 +243,8 @@
+ android:name=".ui.notifications.NotificationsActivity"
+ android:theme="@style/CalypsoTheme" />
{
-
- static public final String NAME = "note";
- static public final String TIMESTAMP_INDEX = "timestamp";
-
- private static final Indexer sTimestampIndexer = new Indexer() {
-
- @Override
- public List index(Note note) {
- List indexes = new ArrayList(1);
- try {
- indexes.add(new Index(TIMESTAMP_INDEX, note.getTimestamp()));
- } catch (NumberFormatException e) {
- // note will not have an indexed timestamp so it will
- // show up at the end of a query sorting by timestamp
- android.util.Log.e("WordPress", "Failed to index timestamp", e);
- }
- return indexes;
- }
-
- };
-
- public Schema() {
- // save an index with a timestamp
- addIndex(sTimestampIndexer);
- }
-
- @Override
- public String getRemoteName() {
- return NAME;
- }
-
- @Override
- public Note build(String key, JSONObject properties) {
- return new Note(properties);
- }
-
- public void update(Note note, JSONObject properties) {
- note.updateJSON(properties);
- }
-
- }
-
-
private static final String TAG = "NoteModel";
// Maximum character length for a comment preview
@@ -78,51 +31,38 @@ public void update(Note note, JSONObject properties) {
private static final String NOTE_UNKNOWN_TYPE = "unknown";
private static final String NOTE_COMMENT_TYPE = "comment";
- public static final String NOTE_COMMENT_LIKE_TYPE = "comment_like";
- public static final String NOTE_LIKE_TYPE = "like";
private static final String NOTE_MATCHER_TYPE = "automattcher";
- private static final String NOTE_ACHIEVEMENT_TYPE = "achievement";
-
- // Notes have different types of "templates" for displaying differently
- // this is not a canonical list but covers all the types currently in use
- private static final String SINGLE_LINE_LIST_TEMPLATE = "single-line-list";
- private static final String MULTI_LINE_LIST_TEMPLATE = "multi-line-list";
- private static final String BIG_BADGE_TEMPLATE = "big-badge";
// JSON action keys
private static final String ACTION_KEY_REPLY = "replyto-comment";
private static final String ACTION_KEY_APPROVE = "approve-comment";
- private static final String ACTION_KEY_UNAPPROVE = "unapprove-comment";
private static final String ACTION_KEY_SPAM = "spam-comment";
+ private static final String ACTION_KEY_LIKE = "like-comment";
+
+ private JSONObject mActions;
+ private JSONObject mNoteJSON;
public static enum EnabledActions {
ACTION_REPLY,
ACTION_APPROVE,
ACTION_UNAPPROVE,
- ACTION_SPAM
+ ACTION_SPAM,
+ ACTION_LIKE
}
- private Map mActions;
- private JSONObject mNoteJSON;
-
- private int mBlogId;
- private int mPostId;
- private long mCommentId;
- private long mCommentParentId;
- private long mTimestamp;
-
- private transient String mCommentPreview;
- private transient String mSubject;
- private transient String mIconUrl;
- private transient String mSnippet;
- private transient String mNoteType;
+ public static enum NoteTimeGroup {
+ GROUP_TODAY,
+ GROUP_YESTERDAY,
+ GROUP_OLDER_TWO_DAYS,
+ GROUP_OLDER_WEEK,
+ GROUP_OLDER_MONTH
+ }
/**
* Create a note using JSON from Simperium
*/
- public Note(JSONObject noteJSON) {
+ private Note(JSONObject noteJSON) {
mNoteJSON = noteJSON;
- preloadContent();
}
/**
@@ -141,23 +81,16 @@ public String getSimperiumKey() {
return getId();
}
- public JSONObject toJSONObject() {
+ private JSONObject toJSONObject() {
return mNoteJSON;
}
public String getId() {
- return queryJSON("id", "0");
+ return String.valueOf(queryJSON("id", 0));
}
- public String getType() {
- if (mNoteType == null) {
- mNoteType = queryJSON("type", NOTE_UNKNOWN_TYPE);
- if (mNoteType.contains(NOTE_ACHIEVEMENT_TYPE)) {
- mNoteType = NOTE_ACHIEVEMENT_TYPE;
- }
- }
-
- return mNoteType;
+ private String getType() {
+ return queryJSON("type", NOTE_UNKNOWN_TYPE);
}
private Boolean isType(String type) {
@@ -165,56 +98,78 @@ private Boolean isType(String type) {
}
public Boolean isCommentType() {
- return isType(NOTE_COMMENT_TYPE);
- }
-
- public Boolean isCommentLikeType() {
- return isType(NOTE_COMMENT_LIKE_TYPE);
+ return (isAutomattcherType() && JSONUtil.queryJSON(mNoteJSON, "meta.ids.comment", -1) != -1) ||
+ isType(NOTE_COMMENT_TYPE);
}
public Boolean isAutomattcherType() {
return isType(NOTE_MATCHER_TYPE);
}
- public String getSubject() {
- if (mSubject == null) {
- String text = queryJSON("subject.text", "").trim();
- if (text.equals("")) {
- text = queryJSON("subject.html", "");
+ private JSONObject getSubject() {
+ try {
+ JSONArray subjectArray = mNoteJSON.getJSONArray("subject");
+ if (subjectArray.length() > 0) {
+ return subjectArray.getJSONObject(0);
}
- mSubject = Html.fromHtml(text).toString();
+ } catch (JSONException e) {
+ return null;
}
- return mSubject;
+
+ return null;
}
- public String getIconURL() {
- if (mIconUrl == null)
- mIconUrl = queryJSON("subject.icon", "");
- return mIconUrl;
+ public Spannable getFormattedSubject() {
+ return NotificationsUtils.getSpannableTextFromIndices(getSubject(), null);
}
- /**
- * Removes HTML and cleans up newlines and whitespace
- */
- public String getCommentPreview() {
- if (mCommentPreview == null) {
- mCommentPreview = HtmlUtils.fastStripHtml(getCommentText());
+ public String getTitle() {
+ return queryJSON("title", "");
+ }
+
+ private String getIconURL() {
+ return queryJSON("icon", "");
+ }
+
+ private String getCommentSubject() {
+ JSONArray subjectArray = mNoteJSON.optJSONArray("subject");
+ if (subjectArray != null) {
+ String commentSubject = JSONUtil.queryJSON(subjectArray, "subject[1].text", "");
// Trim down the comment preview if the comment text is too large.
- if (mCommentPreview.length() > MAX_COMMENT_PREVIEW_LENGTH) {
- mCommentPreview = mCommentPreview.substring(0, MAX_COMMENT_PREVIEW_LENGTH - 1);
+ if (commentSubject != null && commentSubject.length() > MAX_COMMENT_PREVIEW_LENGTH) {
+ commentSubject = commentSubject.substring(0, MAX_COMMENT_PREVIEW_LENGTH - 1);
}
+ return commentSubject;
}
- return mCommentPreview;
+
+ return "";
+ }
+
+ public String getHeaderSnippet() {
+ return queryJSON("header[1].text", "");
}
/**
- * For a comment note the text is in the body object's last item. It currently
- * is only provided in HTML format.
+ * Compare note timestamp to now and return a time grouping
*/
- String getCommentText() {
- return queryJSON("body.items[last].html", "");
+ public static NoteTimeGroup getTimeGroupForTimestamp(long timestamp) {
+ Date today = new Date();
+ Date then = new Date(timestamp * 1000);
+
+ if (then.compareTo(DateUtils.addMonths(today, -1)) < 0) {
+ return NoteTimeGroup.GROUP_OLDER_MONTH;
+ } else if (then.compareTo(DateUtils.addWeeks(today, -1)) < 0) {
+ return NoteTimeGroup.GROUP_OLDER_WEEK;
+ } else if (then.compareTo(DateUtils.addDays(today, -2)) < 0
+ || DateUtils.isSameDay(DateUtils.addDays(today, -2), then)) {
+ return NoteTimeGroup.GROUP_OLDER_TWO_DAYS;
+ } else if (DateUtils.isSameDay(DateUtils.addDays(today, -1), then)) {
+ return NoteTimeGroup.GROUP_YESTERDAY;
+ } else {
+ return NoteTimeGroup.GROUP_TODAY;
+ }
}
/**
@@ -224,121 +179,51 @@ 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
- */
Boolean isRead() {
- return queryJSON("unread", 0) == 0;
+ return queryJSON("read", 0) == 1;
}
- /**
- * Sets the note's 'unread' to 0 and saves it to sync with Simperium
- */
public void markAsRead() {
try {
- mNoteJSON.put("unread", 0);
+ mNoteJSON.put("read", 1);
} catch (JSONException e) {
- Log.e(TAG, "Unable to update note unread property", e);
+ Log.e(TAG, "Unable to update note read property", e);
return;
}
save();
}
- public Reply buildReply(String content) {
- JSONObject replyAction = getActions().get(ACTION_KEY_REPLY);
- String restPath = JSONUtil.queryJSON(replyAction, "params.rest_path", "");
- AppLog.d(T.NOTIFS, String.format("Search actions %s", restPath));
- return new Reply(this, String.format("%s/replies/new", restPath), content);
- }
-
/**
- * Get the timestamp provided by the API for the note - cached for performance
+ * Get the timestamp provided by the API for the note
*/
public long getTimestamp() {
- if (mTimestamp == 0) {
- mTimestamp = queryJSON("timestamp", 0);
- }
-
- return mTimestamp;
- }
-
- /*
- * returns a string representing the timespan based on the note's timestamp - used for display
- * in the notification list (ex: "3d")
- */
- public String getTimeSpan() {
- return DateTimeUtils.timestampToTimeSpan(getTimestamp());
- }
-
- String getTemplate() {
- return queryJSON("body.template", "");
- }
-
- public Boolean isMultiLineListTemplate() {
- return getTemplate().equals(MULTI_LINE_LIST_TEMPLATE);
+ return DateTimeUtils.iso8601ToTimestamp(queryJSON("timestamp", ""));
}
- public Boolean isSingleLineListTemplate() {
- return getTemplate().equals(SINGLE_LINE_LIST_TEMPLATE);
+ public JSONArray getBody() {
+ try {
+ return mNoteJSON.getJSONArray("body");
+ } catch (JSONException e) {
+ return null;
+ }
}
- public Boolean isBigBadgeTemplate() {
- return getTemplate().equals(BIG_BADGE_TEMPLATE);
+ // returns character code for notification font
+ private String getNoticonCharacter() {
+ return queryJSON("noticon", "");
}
- Map getActions() {
+ JSONObject getCommentActions() {
if (mActions == null) {
- try {
- JSONArray actions = queryJSON("body.actions", new JSONArray());
- mActions = new HashMap(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) {
- AppLog.e(T.NOTIFS, "Could not find actions", e);
- mActions = new HashMap();
- }
+ mActions = queryJSON("body[last].actions", new JSONObject());
}
+
return mActions;
}
private void updateJSON(JSONObject json) {
-
mNoteJSON = json;
-
- // clear out the preloaded content
- mTimestamp = 0;
- mCommentPreview = null;
- mSubject = null;
- mIconUrl = null;
- mNoteType = null;
-
- // preload content again
- preloadContent();
- }
-
- /*
- * returns the "meta" section of the note's JSON (not guaranteed to exist)
- */
- private JSONObject getJSONMeta() {
- return JSONUtil.getJSONChild(this.toJSONObject(), "meta");
- }
-
- /*
- * returns the value of the passed name in the meta section of the JSON
- */
- public int getMetaValueAsInt(String name, int defaultValue) {
- JSONObject jsonMeta = getJSONMeta();
- if (jsonMeta == null)
- return defaultValue;
- return jsonMeta.optInt(name, defaultValue);
}
/*
@@ -346,107 +231,139 @@ public int getMetaValueAsInt(String name, int defaultValue) {
*/
public EnumSet getEnabledActions() {
EnumSet actions = EnumSet.noneOf(EnabledActions.class);
- Map jsonActions = getActions();
- if (jsonActions == null || jsonActions.size() == 0)
+ JSONObject jsonActions = getCommentActions();
+ if (jsonActions == null || jsonActions.length() == 0) {
return actions;
- if (jsonActions.containsKey(ACTION_KEY_REPLY))
+ }
+
+ if (jsonActions.has(ACTION_KEY_REPLY)) {
actions.add(EnabledActions.ACTION_REPLY);
- if (jsonActions.containsKey(ACTION_KEY_APPROVE))
- actions.add(EnabledActions.ACTION_APPROVE);
- if (jsonActions.containsKey(ACTION_KEY_UNAPPROVE))
+ }
+ if (jsonActions.has(ACTION_KEY_APPROVE) && jsonActions.optBoolean(ACTION_KEY_APPROVE, false)) {
actions.add(EnabledActions.ACTION_UNAPPROVE);
- if (jsonActions.containsKey(ACTION_KEY_SPAM))
+ }
+ if (jsonActions.has(ACTION_KEY_APPROVE) && !jsonActions.optBoolean(ACTION_KEY_APPROVE, false)) {
+ actions.add(EnabledActions.ACTION_APPROVE);
+ }
+ if (jsonActions.has(ACTION_KEY_SPAM)) {
actions.add(EnabledActions.ACTION_SPAM);
+ }
+ if (jsonActions.has(ACTION_KEY_LIKE)) {
+ actions.add(EnabledActions.ACTION_LIKE);
+ }
+
return actions;
}
- /**
- * pre-loads commonly-accessed fields - avoids performance hit of loading these
- * fields inside an adapter's getView()
- */
- void preloadContent() {
- if (mNoteJSON == null || mNoteJSON.length() == 0) {
- return;
- }
+ public int getSiteId() {
+ return JSONUtil.queryJSON(mNoteJSON, "meta.ids.site", 0);
+ }
- if (isCommentType()) {
- // pre-load the preview text
- getCommentPreview();
- }
+ public int getPostId() {
+ return JSONUtil.queryJSON(mNoteJSON, "meta.ids.post", 0);
+ }
+
+ public long getCommentId() {
+ return JSONUtil.queryJSON(mNoteJSON, "meta.ids.comment", 0);
+ }
- // pre-load the subject, avatar url and type
- getSubject();
- getIconURL();
- getType();
- // pre-load site/post/comment IDs
- preloadMetaIds();
+ public long getParentCommentId() {
+ return JSONUtil.queryJSON(mNoteJSON, "meta.ids.parent_comment", 0);
}
- /*
- * nbradbury - preload the blog, post, & comment IDs from the meta section
- * ids={"site":61509427,"self":993925505,"post":161,"comment":178,"comment_parent":0}
+ /**
+ * Rudimentary system for pulling an item out of a JSON object hierarchy
*/
- private void preloadMetaIds() {
- JSONObject jsonMeta = getJSONMeta();
- if (jsonMeta == null)
- return;
- JSONObject jsonIDs = jsonMeta.optJSONObject("ids");
- if (jsonIDs == null)
- return;
- mBlogId = jsonIDs.optInt("site");
- mPostId = jsonIDs.optInt("post");
- mCommentId = jsonIDs.optLong("comment");
- mCommentParentId = jsonIDs.optLong("comment_parent");
+ private U queryJSON(String query, U defaultObject) {
+ return JSONUtil.queryJSON(this.toJSONObject(), query, defaultObject);
}
- public int getBlogId() {
- return mBlogId;
- }
+ /**
+ * Constructs a new Comment object based off of data in a Note
+ */
+ public Comment buildComment() {
+ return new Comment(
+ getPostId(),
+ getCommentId(),
+ getCommentAuthorName(),
+ DateTimeUtils.timestampToIso8601Str(getTimestamp()),
+ getCommentText(),
+ CommentStatus.toString(getCommentStatus()),
+ "", // post title is unavailable in note model
+ getCommentAuthorUrl(),
+ "", // user email is unavailable in note model
+ getIconURL()
+ );
+ }
+
+ public String getCommentAuthorName() {
+ JSONArray bodyArray = getBody();
+
+ for (int i=0; i < bodyArray.length(); i++) {
+ try {
+ JSONObject bodyItem = bodyArray.getJSONObject(i);
+ if (bodyItem.has("type") && bodyItem.optString("type").equals("user")) {
+ return bodyItem.optString("text");
+ }
+ } catch (JSONException e) {
+ return "";
+ }
+ }
- public int getPostId() {
- return mPostId;
+ return "";
}
- public long getCommentId() {
- return mCommentId;
+ private String getCommentText() {
+ return queryJSON("body[last].text", "");
}
- public long getCommentParentId() {
- return mCommentParentId;
+ private String getCommentAuthorUrl() {
+ JSONArray bodyArray = getBody();
+
+ for (int i=0; i < bodyArray.length(); i++) {
+ try {
+ JSONObject bodyItem = bodyArray.getJSONObject(i);
+ if (bodyItem.has("type") && bodyItem.optString("type").equals("user")) {
+ return JSONUtil.queryJSON(bodyItem, "meta.links.home", "");
+ }
+ } catch (JSONException e) {
+ return "";
+ }
+ }
+
+ return "";
}
- /*
- * plain-text snippet returned by the server - currently shown only for comments
- */
- String getSnippet() {
- if (mSnippet == null) {
- mSnippet = queryJSON("snippet", "");
+ public CommentStatus getCommentStatus() {
+ EnumSet enabledActions = getEnabledActions();
+
+ if (enabledActions.contains(EnabledActions.ACTION_UNAPPROVE)) {
+ return CommentStatus.APPROVED;
+ } else if (enabledActions.contains(EnabledActions.ACTION_APPROVE)) {
+ return CommentStatus.UNAPPROVED;
}
- return mSnippet;
+
+ return CommentStatus.UNKNOWN;
}
- public boolean hasSnippet() {
- return !TextUtils.isEmpty(getSnippet());
+ public boolean hasLikedComment() {
+ JSONObject jsonActions = getCommentActions();
+ return !(jsonActions == null || jsonActions.length() == 0) && jsonActions.optBoolean(ACTION_KEY_LIKE);
}
- /**
- * Rudimentary system for pulling an item out of a JSON object hierarchy
- */
- public U queryJSON(String query, U defaultObject) {
- return JSONUtil.queryJSON(this.toJSONObject(), query, defaultObject);
+ public JSONArray getHeader() {
+ return mNoteJSON.optJSONArray("header");
}
/**
* Represents a user replying to a note.
*/
public static class Reply {
- private final Note mNote;
private final String mContent;
private final String mRestPath;
- Reply(Note note, String restPath, String content) {
- mNote = note;
+ Reply(String restPath, String content) {
mRestPath = restPath;
mContent = content;
}
@@ -459,4 +376,71 @@ public String getRestPath() {
return mRestPath;
}
}
+
+ public Reply buildReply(String content) {
+ String restPath;
+ if (this.isCommentType()) {
+ restPath = String.format("sites/%d/comments/%d", getSiteId(), getCommentId());
+ } else {
+ restPath = String.format("sites/%d/posts/%d", getSiteId(), getPostId());
+ }
+
+ return new Reply(String.format("%s/replies/new", restPath), content);
+ }
+
+ /**
+ * Simperium Schema
+ */
+ public static class Schema extends BucketSchema {
+
+ static public final String NAME = "note20";
+ static public final String TIMESTAMP_INDEX = "timestamp";
+ static public final String SUBJECT_INDEX = "subject";
+ static public final String SNIPPET_INDEX = "snippet";
+ static public final String UNREAD_INDEX = "unread";
+ static public final String NOTICON_INDEX = "noticon";
+ static public final String ICON_URL_INDEX = "icon";
+
+ private static final Indexer sNoteIndexer = new Indexer() {
+
+ @Override
+ public List index(Note note) {
+ List indexes = new ArrayList();
+ try {
+ indexes.add(new Index(TIMESTAMP_INDEX, note.getTimestamp()));
+ } catch (NumberFormatException e) {
+ // note will not have an indexed timestamp so it will
+ // show up at the end of a query sorting by timestamp
+ android.util.Log.e("WordPress", "Failed to index timestamp", e);
+ }
+
+ indexes.add(new Index(SUBJECT_INDEX, Html.toHtml(note.getFormattedSubject())));
+ indexes.add(new Index(SNIPPET_INDEX, note.getCommentSubject()));
+ indexes.add(new Index(UNREAD_INDEX, note.isUnread()));
+ indexes.add(new Index(NOTICON_INDEX, note.getNoticonCharacter()));
+ indexes.add(new Index(ICON_URL_INDEX, note.getIconURL()));
+
+ return indexes;
+ }
+
+ };
+
+ public Schema() {
+ addIndex(sNoteIndexer);
+ }
+
+ @Override
+ public String getRemoteName() {
+ return NAME;
+ }
+
+ @Override
+ public Note build(String key, JSONObject properties) {
+ return new Note(properties);
+ }
+
+ public void update(Note note, JSONObject properties) {
+ note.updateJSON(properties);
+ }
+ }
}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticator.java b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticator.java
index 009634c4cdcc..ce65b63e7232 100644
--- a/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticator.java
+++ b/WordPress/src/main/java/org/wordpress/android/networking/OAuthAuthenticator.java
@@ -11,7 +11,7 @@
import org.wordpress.android.WordPress;
import org.wordpress.android.WordPressDB;
import org.wordpress.android.models.Blog;
-import org.wordpress.android.ui.notifications.SimperiumUtils;
+import org.wordpress.android.ui.notifications.utils.SimperiumUtils;
public class OAuthAuthenticator implements Authenticator {
@Override
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/WPActionBarActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/WPActionBarActivity.java
index 15e59b2495e3..771849811dfb 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/WPActionBarActivity.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/WPActionBarActivity.java
@@ -46,7 +46,6 @@
import org.wordpress.android.ui.comments.CommentsActivity;
import org.wordpress.android.ui.media.MediaBrowserActivity;
import org.wordpress.android.ui.notifications.NotificationsActivity;
-import org.wordpress.android.ui.notifications.SimperiumUtils;
import org.wordpress.android.ui.posts.EditPostActivity;
import org.wordpress.android.ui.posts.PagesActivity;
import org.wordpress.android.ui.posts.PostsActivity;
@@ -60,7 +59,7 @@
import org.wordpress.android.util.BlogUtils;
import org.wordpress.android.util.DeviceUtils;
import org.wordpress.android.util.DisplayUtils;
-import org.wordpress.android.util.StringUtils;
+import org.wordpress.android.ui.notifications.utils.SimperiumUtils;
import org.wordpress.android.util.ToastUtils;
import org.wordpress.android.util.ToastUtils.Duration;
import org.wordpress.android.util.ptr.PullToRefreshHelper;
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentActions.java b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentActions.java
index 669423004fcc..754b52de7c0c 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentActions.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentActions.java
@@ -32,6 +32,7 @@
*/
public class CommentActions {
+
private CommentActions() {
throw new AssertionError();
}
@@ -55,11 +56,19 @@ public interface OnCommentsModeratedListener {
* comments (moderated, deleted, added, etc.)
*/
public static enum ChangedFrom {COMMENT_LIST, COMMENT_DETAIL}
- public static enum ChangeType {EDITED, STATUS, REPLIED, TRASHED}
+ public static enum ChangeType {EDITED, STATUS, REPLIED, TRASHED, SPAMMED}
public static interface OnCommentChangeListener {
public void onCommentChanged(ChangedFrom changedFrom, ChangeType changeType);
}
+ public static interface OnCommentActionListener {
+ public void onModerateComment(int accountId, Comment comment, CommentStatus newStatus);
+ }
+
+ public static interface OnNoteCommentActionListener {
+ public void onModerateCommentForNote(Note note, CommentStatus newStatus);
+ }
+
/*
* add a comment for the passed post
@@ -202,7 +211,7 @@ public void run() {
* submitReplyToComment() in that it enables responding to a reply to a comment this
* user made on someone else's blog
*/
- static void submitReplyToCommentNote(final Note note,
+ public static void submitReplyToCommentNote(final Note note,
final String replyText,
final CommentActionListener actionListener) {
if (note == null || TextUtils.isEmpty(replyText)) {
@@ -232,13 +241,75 @@ public void onErrorResponse(VolleyError volleyError) {
WordPress.getRestClientUtils().replyToComment(reply.getContent(), reply.getRestPath(), listener, errorListener);
}
+ /**
+ * reply to an individual comment via the WP.com REST API
+ */
+ public static void submitReplyToCommentRestApi(long siteId, long commentId,
+ final String replyText,
+ final CommentActionListener actionListener) {
+ if (TextUtils.isEmpty(replyText)) {
+ if (actionListener != null)
+ actionListener.onActionResult(false);
+ return;
+ }
+
+ RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (actionListener != null)
+ actionListener.onActionResult(true);
+ }
+ };
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ if (volleyError != null)
+ AppLog.e(T.COMMENTS, volleyError.getMessage(), volleyError);
+ if (actionListener != null)
+ actionListener.onActionResult(false);
+ }
+ };
+
+ WordPress.getRestClientUtils().replyToComment(siteId, commentId, replyText, listener, errorListener);
+ }
+
+ /**
+ * Moderate a comment from a WPCOM notification
+ */
+ public static void moderateCommentRestApi(long siteId,
+ long commentId,
+ CommentStatus newStatus,
+ final CommentActionListener actionListener) {
+
+ WordPress.getRestClientUtils().moderateComment(
+ String.valueOf(siteId),
+ String.valueOf(commentId),
+ CommentStatus.toRESTString(newStatus),
+ new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject response) {
+ if (actionListener != null) {
+ actionListener.onActionResult(true);
+ }
+ }
+ }, new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ if (actionListener != null) {
+ actionListener.onActionResult(false);
+ }
+ }
+ }
+ );
+ }
+
/**
* Moderate a comment from a WPCOM notification
*/
public static void moderateCommentForNote(Note note, CommentStatus newStatus, final CommentActionListener actionListener) {
WordPress.getRestClientUtils().moderateComment(
- String.valueOf(note.getBlogId()),
+ String.valueOf(note.getSiteId()),
String.valueOf(note.getCommentId()),
CommentStatus.toRESTString(newStatus),
new RestRequest.Listener() {
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentAdapter.java
index 54a91c6a09d3..b5b63129ea39 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentAdapter.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentAdapter.java
@@ -21,7 +21,9 @@
import org.wordpress.android.util.DateTimeUtils;
import org.wordpress.android.widgets.WPNetworkImageView;
+import java.util.ArrayList;
import java.util.HashSet;
+import java.util.List;
class CommentAdapter extends BaseAdapter {
static interface DataLoadedListener {
@@ -43,16 +45,16 @@ static interface OnSelectedItemsChangeListener {
private CommentList mComments = new CommentList();
private final HashSet mSelectedPositions = new HashSet();
+ private final List mModeratingCommentsIds = new ArrayList();
private final int mStatusColorSpam;
private final int mStatusColorUnapproved;
- private final int mSelectionColor;
private final int mAvatarSz;
- private long mHighlightedCommentId = -1;
private final String mStatusTextSpam;
private final String mStatusTextUnapproved;
+ private final int mSelectionColor;
private boolean mEnableSelection;
@@ -168,19 +170,20 @@ void toggleItemSelected(int position, View view) {
setItemSelected(position, !isItemSelected(position), view);
}
- /*
- * this is used for tablet UI to highlight the comment displayed in the detail view
- */
- long getHighlightedCommentId() {
- return mHighlightedCommentId;
+ public void addModeratingCommentId(long commentId) {
+ mModeratingCommentsIds.add(commentId);
+ notifyDataSetChanged();
}
- void setHighlightedCommentId(long commentId) {
- if (mHighlightedCommentId == commentId)
- return;
- mHighlightedCommentId = commentId;
+
+ public void removeModeratingCommentId(long commentId) {
+ mModeratingCommentsIds.remove(commentId);
notifyDataSetChanged();
}
+ public boolean isModeratingCommentId(long commentId) {
+ return mModeratingCommentsIds.size() > 0 && mModeratingCommentsIds.contains(commentId);
+ }
+
public int indexOfCommentId(long commentId) {
return mComments.indexOfCommentId(commentId);
}
@@ -199,6 +202,14 @@ void deleteComments(final CommentList comments) {
notifyDataSetChanged();
}
+ public void removeComment(Comment comment) {
+ int commentIndex = indexOfCommentId(comment.commentID);
+ if (commentIndex >= 0) {
+ mComments.remove(commentIndex);
+ notifyDataSetChanged();
+ }
+ }
+
public View getView(final int position, View convertView, ViewGroup parent) {
final Comment comment = mComments.get(position);
final CommentHolder holder;
@@ -211,6 +222,12 @@ public View getView(final int position, View convertView, ViewGroup parent) {
holder = (CommentHolder) convertView.getTag();
}
+ if (isModeratingCommentId(comment.commentID)) {
+ holder.progressBar.setVisibility(View.VISIBLE);
+ } else {
+ holder.progressBar.setVisibility(View.GONE);
+ }
+
holder.txtTitle.setText(Html.fromHtml(comment.getFormattedTitle()));
holder.txtComment.setText(comment.getUnescapedCommentText());
holder.txtDate.setText(DateTimeUtils.javaDateToTimeSpan(comment.getDatePublished()));
@@ -234,13 +251,12 @@ public View getView(final int position, View convertView, ViewGroup parent) {
}
holder.txtStatus.setVisibility(showStatus ? View.VISIBLE : View.GONE);
- final boolean useSelectionBackground;
+ boolean useSelectionBackground = false;
if (mEnableSelection && isItemSelected(position)) {
useSelectionBackground = true;
if (holder.imgCheckmark.getVisibility() != View.VISIBLE)
holder.imgCheckmark.setVisibility(View.VISIBLE);
} else {
- useSelectionBackground = (mHighlightedCommentId == comment.commentID);
if (holder.imgCheckmark.getVisibility() == View.VISIBLE)
holder.imgCheckmark.setVisibility(View.GONE);
holder.imgAvatar.setImageUrl(comment.getAvatarForDisplay(mAvatarSz), WPNetworkImageView.ImageType.AVATAR);
@@ -278,6 +294,7 @@ private class CommentHolder {
private final TextView txtDate;
private final WPNetworkImageView imgAvatar;
private final ImageView imgCheckmark;
+ private final View progressBar;
private CommentHolder(View row) {
txtTitle = (TextView) row.findViewById(R.id.title);
@@ -286,6 +303,7 @@ private CommentHolder(View row) {
txtDate = (TextView) row.findViewById(R.id.text_date);
imgCheckmark = (ImageView) row.findViewById(R.id.image_checkmark);
imgAvatar = (WPNetworkImageView) row.findViewById(R.id.avatar);
+ progressBar = row.findViewById(R.id.moderate_progress);
}
}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentDetailActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentDetailActivity.java
new file mode 100644
index 000000000000..97a455460e35
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentDetailActivity.java
@@ -0,0 +1,85 @@
+package org.wordpress.android.ui.comments;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import com.simperium.client.BucketObjectMissingException;
+
+import org.wordpress.android.R;
+import org.wordpress.android.models.Note;
+import org.wordpress.android.ui.notifications.utils.SimperiumUtils;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.ToastUtils;
+
+// simple wrapper activity for CommentDetailFragment
+public class CommentDetailActivity extends Activity {
+
+ public static final String KEY_COMMENT_DETAIL_LOCAL_TABLE_BLOG_ID = "local_table_blog_id";
+ public static final String KEY_COMMENT_DETAIL_COMMENT_ID = "comment_detail_comment_id";
+ public static final String KEY_COMMENT_DETAIL_NOTE_ID = "comment_detail_note_id";
+ public static final String KEY_COMMENT_DETAIL_IS_REMOTE = "comment_detail_is_remote";
+
+ private static final String TAG_COMMENT_DETAIL_FRAGMENT = "tag_comment_detail_fragment";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.comment_activity_detail);
+
+ setTitle(R.string.comment);
+
+ if (getActionBar() != null) {
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ if (savedInstanceState == null) {
+ Intent intent = getIntent();
+ CommentDetailFragment commentDetailFragment = null;
+ if (intent.getStringExtra(KEY_COMMENT_DETAIL_NOTE_ID) != null && SimperiumUtils.getNotesBucket() != null) {
+ try {
+ Note note = SimperiumUtils.getNotesBucket().get(
+ intent.getStringExtra(KEY_COMMENT_DETAIL_NOTE_ID)
+ );
+
+ if (intent.hasExtra(KEY_COMMENT_DETAIL_IS_REMOTE)) {
+ commentDetailFragment = CommentDetailFragment.newInstanceForRemoteNoteComment(note);
+ } else {
+ commentDetailFragment = CommentDetailFragment.newInstance(note);
+ }
+ } catch (BucketObjectMissingException e) {
+ AppLog.e(AppLog.T.NOTIFS, "CommentDetailActivity was passed an invalid note id.");
+ }
+ } else if (intent.getIntExtra(KEY_COMMENT_DETAIL_LOCAL_TABLE_BLOG_ID, 0) > 0
+ && intent.getLongExtra(KEY_COMMENT_DETAIL_COMMENT_ID, 0) > 0) {
+ commentDetailFragment = CommentDetailFragment.newInstance(
+ intent.getIntExtra(KEY_COMMENT_DETAIL_LOCAL_TABLE_BLOG_ID, 0),
+ intent.getLongExtra(KEY_COMMENT_DETAIL_COMMENT_ID, 0)
+ );
+ }
+
+ if (commentDetailFragment != null) {
+ commentDetailFragment.setRetainInstance(true);
+ getFragmentManager().beginTransaction()
+ .add(R.id.comment_detail_container, commentDetailFragment, TAG_COMMENT_DETAIL_FRAGMENT)
+ .commit();
+ } else {
+ ToastUtils.showToast(this, R.string.error_load_comment);
+ finish();
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentDetailFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentDetailFragment.java
index 7902136e8208..74d8ca0884a7 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentDetailFragment.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentDetailFragment.java
@@ -1,20 +1,22 @@
package org.wordpress.android.ui.comments;
import android.app.Activity;
-import android.app.AlertDialog;
import android.app.Fragment;
-import android.content.DialogInterface;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.text.Html;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageView;
+import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -35,8 +37,12 @@
import org.wordpress.android.ui.comments.CommentActions.ChangeType;
import org.wordpress.android.ui.comments.CommentActions.ChangedFrom;
import org.wordpress.android.ui.comments.CommentActions.OnCommentChangeListener;
+import org.wordpress.android.ui.comments.CommentActions.OnCommentActionListener;
+import org.wordpress.android.ui.comments.CommentActions.OnNoteCommentActionListener;
import org.wordpress.android.ui.notifications.NotificationFragment;
+import org.wordpress.android.ui.notifications.NotificationsDetailListFragment;
import org.wordpress.android.ui.reader.ReaderActivityLauncher;
+import org.wordpress.android.ui.reader.ReaderAnim;
import org.wordpress.android.ui.reader.actions.ReaderActions;
import org.wordpress.android.ui.reader.actions.ReaderPostActions;
import org.wordpress.android.util.AniUtils;
@@ -74,18 +80,37 @@ public class CommentDetailFragment extends Fragment implements NotificationFragm
private ViewGroup mLayoutReply;
private ViewGroup mLayoutButtons;
- private TextView mBtnModerateComment;
+ private View mBtnLikeComment;
+ private ImageView mBtnLikeIcon;
+ private TextView mBtnLikeTextView;
+
+ private View mBtnModerateComment;
+ private ImageView mBtnModerateIcon;
+ private TextView mBtnModerateTextView;
+
private TextView mBtnSpamComment;
- private TextView mBtnEditComment;
private TextView mBtnTrashComment;
+ private TextView mBtnEditComment;
+ private TextView mBtnMore;
+
+ private String mRestoredReplyText;
- private boolean mIsSubmittingReply = false;
- private boolean mIsModeratingComment = false;
- private boolean mIsRequestingComment = false;
private boolean mIsUsersBlog = false;
+ /*
+ * Used to request a comment from a note using its site and comment ids, rather than build
+ * the comment with the content in the note. See showComment()
+ */
+ private boolean mShouldRequestCommentFromNote = false;
+
+ private boolean mIsSubmittingReply = false;
+
+ private NotificationsDetailListFragment mNotificationsDetailListFragment;
+
private OnCommentChangeListener mOnCommentChangeListener;
private OnPostClickListener mOnPostClickListener;
+ private OnCommentActionListener mOnCommentActionListener;
+ private OnNoteCommentActionListener mOnNoteCommentActionListener;
private static final String KEY_LOCAL_BLOG_ID = "local_blog_id";
private static final String KEY_COMMENT_ID = "comment_id";
@@ -115,6 +140,15 @@ public static CommentDetailFragment newInstance(final Note note) {
return fragment;
}
+ /*
+ * used when called from notifications to load a comment that doesn't already exist in the note
+ */
+ public static CommentDetailFragment newInstanceForRemoteNoteComment(final Note note) {
+ CommentDetailFragment fragment = newInstance(note);
+ fragment.setShouldRequestCommentFromNote(true);
+ return fragment;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -141,20 +175,29 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
mTxtStatus = (TextView) view.findViewById(R.id.text_status);
mTxtContent = (TextView) view.findViewById(R.id.text_content);
- mLayoutButtons = (ViewGroup) view.findViewById(R.id.layout_buttons);
- mBtnModerateComment = (TextView) mLayoutButtons.findViewById(R.id.text_btn_moderate);
+ mLayoutButtons = (ViewGroup) inflater.inflate(R.layout.comment_action_footer, null, false);
+ mBtnLikeComment = mLayoutButtons.findViewById(R.id.btn_like);
+ mBtnLikeIcon = (ImageView)mLayoutButtons.findViewById(R.id.btn_like_icon);
+ mBtnLikeTextView = (TextView)mLayoutButtons.findViewById(R.id.btn_like_text);
+ mBtnModerateComment = mLayoutButtons.findViewById(R.id.btn_moderate);
+ mBtnModerateIcon = (ImageView)mLayoutButtons.findViewById(R.id.btn_moderate_icon);
+ mBtnModerateTextView = (TextView)mLayoutButtons.findViewById(R.id.btn_moderate_text);
mBtnSpamComment = (TextView) mLayoutButtons.findViewById(R.id.text_btn_spam);
- mBtnEditComment = (TextView) mLayoutButtons.findViewById(R.id.image_edit_comment);
mBtnTrashComment = (TextView) mLayoutButtons.findViewById(R.id.image_trash_comment);
+ mBtnEditComment = (TextView) mLayoutButtons.findViewById(R.id.image_edit_comment);
+ mBtnMore = (TextView)mLayoutButtons.findViewById(R.id.text_btn_more);
- setTextDrawable(mBtnSpamComment, R.drawable.ic_cab_spam);
- setTextDrawable(mBtnEditComment, R.drawable.ab_icon_edit);
- setTextDrawable(mBtnTrashComment, R.drawable.ic_cab_trash);
+ setTextDrawable(mBtnSpamComment, R.drawable.ic_action_spam);
+ setTextDrawable(mBtnTrashComment, R.drawable.ic_action_trash);
+ setTextDrawable(mBtnEditComment, R.drawable.ic_action_edit);
mLayoutReply = (ViewGroup) view.findViewById(R.id.layout_comment_box);
mEditReply = (EditText) mLayoutReply.findViewById(R.id.edit_comment);
mImgSubmitReply = (ImageView) mLayoutReply.findViewById(R.id.image_post_comment);
+ // hide comment like button until we know it can be enabled in showCommentForNote()
+ mBtnLikeComment.setVisibility(View.GONE);
+
// hide moderation buttons until updateModerationButtons() is called
mLayoutButtons.setVisibility(View.GONE);
mBtnEditComment.setVisibility(View.GONE);
@@ -173,6 +216,11 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
}
});
+ if (!TextUtils.isEmpty(mRestoredReplyText)) {
+ mEditReply.setText(mRestoredReplyText);
+ mRestoredReplyText = null;
+ }
+
mImgSubmitReply.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -183,11 +231,7 @@ public void onClick(View v) {
mBtnSpamComment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (mComment.getStatusEnum() == CommentStatus.SPAM) {
- moderateComment(CommentStatus.APPROVED);
- } else {
- moderateComment(CommentStatus.SPAM);
- }
+ moderateComment(CommentStatus.SPAM);
}
});
@@ -198,16 +242,60 @@ public void onClick(View v) {
}
});
+
mBtnTrashComment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- confirmDeleteComment();
+ moderateComment(CommentStatus.TRASH);
}
});
+ mBtnLikeComment.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ likeComment();
+ }
+ });
+
+ mBtnMore.setOnClickListener(mMoreBtnClickListener);
+
return view;
}
+ private View.OnClickListener mMoreBtnClickListener = new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ PopupMenu popup = new PopupMenu(getActivity(), mBtnMore);
+
+ if (canMarkAsSpam() && mBtnSpamComment.getVisibility() == View.GONE) {
+ final MenuItem menuItem = popup.getMenu().add(mBtnSpamComment.getText());
+ menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ moderateComment(CommentStatus.SPAM);
+ return true;
+ }
+ });
+ }
+
+ if (canEdit() && mBtnEditComment.getVisibility() == View.GONE) {
+ MenuItem menuItem = popup.getMenu().add(getString(R.string.mnu_comment_edit));
+ menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ editComment();
+ return true;
+ }
+ });
+ }
+
+ if (popup.getMenu().size() > 0) {
+ popup.show();
+ }
+ }
+ };
+
void setComment(int localBlogId, long commentId) {
setComment(localBlogId, CommentTable.getComment(localBlogId, commentId));
}
@@ -227,6 +315,10 @@ private void setComment(int localBlogId, final Comment comment) {
showComment();
}
+ void setShouldRequestCommentFromNote(boolean shouldRequestComment) {
+ mShouldRequestCommentFromNote = shouldRequestComment;
+ }
+
@Override
public Note getNote() {
return mNote;
@@ -235,8 +327,9 @@ public Note getNote() {
@Override
public void setNote(Note note) {
mNote = note;
- if (isAdded() && mNote != null)
+ if (isAdded() && mNote != null) {
showComment();
+ }
}
public void onAttach(Activity activity) {
@@ -245,6 +338,10 @@ public void onAttach(Activity activity) {
mOnCommentChangeListener = (OnCommentChangeListener) activity;
if (activity instanceof OnPostClickListener)
mOnPostClickListener = (OnPostClickListener) activity;
+ if (activity instanceof OnCommentActionListener)
+ mOnCommentActionListener = (OnCommentActionListener) activity;
+ if (activity instanceof OnNoteCommentActionListener)
+ mOnNoteCommentActionListener = (OnNoteCommentActionListener) activity;
}
@Override
@@ -256,6 +353,10 @@ public void onStart() {
@Override
public void onPause() {
super.onPause();
+ // Reset comment if this is from a notification
+ if (mNote != null) {
+ mComment = null;
+ }
EditTextUtils.hideSoftInput(mEditReply);
}
@@ -263,7 +364,9 @@ public void onPause() {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == Constants.INTENT_COMMENT_EDITOR && resultCode == Activity.RESULT_OK) {
- reloadComment();
+ if (mNote == null) {
+ reloadComment();
+ }
// tell the host to reload the comment list
if (mOnCommentChangeListener != null)
mOnCommentChangeListener.onCommentChanged(ChangedFrom.COMMENT_DETAIL, ChangeType.EDITED);
@@ -286,6 +389,10 @@ private int getRemoteBlogId() {
return mRemoteBlogId;
}
+ public void setRestoredReplyText(String restoredReplyText) {
+ mRestoredReplyText = restoredReplyText;
+ }
+
/*
* reload the current comment from the local database
*/
@@ -316,6 +423,9 @@ private void editComment() {
Intent intent = new Intent(getActivity(), EditCommentActivity.class);
intent.putExtra(EditCommentActivity.ARG_LOCAL_BLOG_ID, getLocalBlogId());
intent.putExtra(EditCommentActivity.ARG_COMMENT_ID, getCommentId());
+ if (mNote != null) {
+ intent.putExtra(EditCommentActivity.ARG_NOTE_ID, mNote.getId());
+ }
startActivityForResult(intent, Constants.INTENT_COMMENT_EDITOR);
}
@@ -335,9 +445,23 @@ private void showComment() {
scrollView.setVisibility(View.GONE);
layoutBottom.setVisibility(View.GONE);
- // if a notification was passed, request its associated comment
- if (mNote != null && !mIsRequestingComment)
+ if (mNote != null && mShouldRequestCommentFromNote) {
+ // If a remote comment was requested, check if we have the comment for display.
+ // Otherwise request the comment via the REST API
+ int localTableBlogId = WordPress.wpDB.getLocalTableBlogIdForRemoteBlogId(mNote.getSiteId());
+ if (localTableBlogId > 0) {
+ Comment comment = CommentTable.getComment(localTableBlogId, mNote.getParentCommentId());
+ if (comment != null) {
+ setComment(localTableBlogId, comment);
+ return;
+ }
+ }
+
+ long commentId = mNote.getParentCommentId() > 0 ? mNote.getParentCommentId() : mNote.getCommentId();
+ requestComment(localTableBlogId, mNote.getSiteId(), commentId);
+ } else if (mNote != null) {
showCommentForNote(mNote);
+ }
return;
}
@@ -345,6 +469,12 @@ private void showComment() {
scrollView.setVisibility(View.VISIBLE);
layoutBottom.setVisibility(View.VISIBLE);
+ // Add action buttons footer
+ if ((mNote == null || mShouldRequestCommentFromNote) && mLayoutButtons.getParent() == null) {
+ ViewGroup commentContentLayout = (ViewGroup) getView().findViewById(R.id.comment_content_container);
+ commentContentLayout.addView(mLayoutButtons);
+ }
+
final WPNetworkImageView imgAvatar = (WPNetworkImageView) getView().findViewById(R.id.image_avatar);
final TextView txtName = (TextView) getView().findViewById(R.id.text_name);
final TextView txtDate = (TextView) getView().findViewById(R.id.text_date);
@@ -494,117 +624,43 @@ public void onClick(View v) {
}
}
- private void confirmDeleteComment() {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setMessage(R.string.dlg_confirm_trash_comments);
- builder.setTitle(R.string.trash);
- builder.setCancelable(true);
- builder.setPositiveButton(R.string.trash_yes, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- moderateComment(CommentStatus.TRASH);
- }
- });
- builder.setNegativeButton(R.string.trash_no, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
- AlertDialog alert = builder.create();
- alert.show();
- }
-
- private void dismissDialog(int id) {
- if (!isAdded())
- return;
- try {
- getActivity().dismissDialog(id);
- } catch (IllegalArgumentException e) {
- // raised when dialog wasn't created
- }
- }
-
/*
* approve, unapprove, spam, or trash the current comment
*/
private void moderateComment(final CommentStatus newStatus) {
- if (!isAdded() || !hasComment() || mIsModeratingComment)
+ if (!isAdded() || !hasComment())
return;
if (!NetworkUtils.checkConnection(getActivity()))
return;
- // show dialog while moderating
- final int dlgId;
- switch (newStatus) {
- case APPROVED:
- dlgId = CommentDialogs.ID_COMMENT_DLG_APPROVING;
- AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATION_APPROVED);
- break;
- case UNAPPROVED:
- dlgId = CommentDialogs.ID_COMMENT_DLG_UNAPPROVING;
- break;
- case SPAM:
- dlgId = CommentDialogs.ID_COMMENT_DLG_SPAMMING;
- AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATION_FLAGGED_AS_SPAM);
- break;
- case TRASH:
- dlgId = CommentDialogs.ID_COMMENT_DLG_TRASHING;
- AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATION_TRASHED);
- break;
- default :
- return;
+ // Fire the appropriate listener if we have one
+ if (mNote != null && mOnNoteCommentActionListener != null) {
+ mOnNoteCommentActionListener.onModerateCommentForNote(mNote, newStatus);
+ return;
+ } else if (mOnCommentActionListener != null) {
+ mOnCommentActionListener.onModerateComment(mLocalBlogId, mComment, newStatus);
+ return;
}
- getActivity().showDialog(dlgId);
-
- // disable buttons during request
- mLayoutButtons.setEnabled(false);
- // animate the buttons out (updateStatusViews will re-display them when request completes)
- mLayoutButtons.clearAnimation();
- AniUtils.flyOut(mLayoutButtons);
+ if (mNote == null) return;
- // hide status (updateStatusViews will un-hide it)
- if (mTxtStatus.getVisibility() == View.VISIBLE) {
- mTxtStatus.clearAnimation();
- AniUtils.startAnimation(mTxtStatus, R.anim.fade_out);
- mTxtStatus.setVisibility(View.INVISIBLE);
- }
-
- CommentActions.CommentActionListener actionListener = new CommentActions.CommentActionListener() {
+ // Basic moderation support, currently only used when this Fragment is in a CommentDetailActivity
+ // Uses WP.com REST API and requires a note object
+ final CommentStatus oldStatus = mComment.getStatusEnum();
+ mComment.setStatus(CommentStatus.toString(newStatus));
+ updateStatusViews();
+ CommentActions.moderateCommentRestApi(mNote.getSiteId(), mComment.commentID, newStatus, new CommentActions.CommentActionListener() {
@Override
public void onActionResult(boolean succeeded) {
- mIsModeratingComment = false;
- if (isAdded()) {
- dismissDialog(dlgId);
- mLayoutButtons.setEnabled(true);
- if (succeeded) {
- mComment.setStatus(CommentStatus.toString(newStatus));
- } else {
- ToastUtils.showToast(getActivity(), R.string.error_moderate_comment, ToastUtils.Duration.LONG);
- }
- if (newStatus == CommentStatus.TRASH) {
- // clear the comment if it was trashed
- clear();
- } else {
- // reflect the new status - note this MUST come after mComment.setStatus
- updateStatusViews();
- }
- }
+ if (!isAdded()) return;
- if (succeeded && mOnCommentChangeListener != null) {
- ChangeType changeType = (newStatus == CommentStatus.TRASH ? ChangeType.TRASHED : ChangeType.STATUS);
- mOnCommentChangeListener.onCommentChanged(ChangedFrom.COMMENT_DETAIL, changeType);
+ if (!succeeded) {
+ mComment.setStatus(CommentStatus.toString(oldStatus));
+ updateStatusViews();
+ ToastUtils.showToast(getActivity(), R.string.error_moderate_comment);
}
}
- };
- mIsModeratingComment = true;
- // Moderate notifications via REST API, otherwise over XML-RPC
- if (mNote != null) {
- CommentActions.moderateCommentForNote(mNote, newStatus, actionListener);
- } else {
- CommentActions.moderateComment(mLocalBlogId, mComment, newStatus, actionListener);
- }
+ });
}
/*
@@ -628,10 +684,6 @@ private void submitReply() {
final ProgressBar progress = (ProgressBar) getView().findViewById(R.id.progress_submit_comment);
progress.setVisibility(View.VISIBLE);
- // animate the buttons out (updateStatusViews will re-display them when request completes)
- mLayoutButtons.clearAnimation();
- AniUtils.flyOut(mLayoutButtons);
-
CommentActions.CommentActionListener actionListener = new CommentActions.CommentActionListener() {
@Override
public void onActionResult(boolean succeeded) {
@@ -659,7 +711,11 @@ public void onActionResult(boolean succeeded) {
AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATION_REPLIED_TO);
if (mNote != null) {
- CommentActions.submitReplyToCommentNote(mNote, replyText, actionListener);
+ if (mShouldRequestCommentFromNote) {
+ CommentActions.submitReplyToCommentRestApi(mNote.getSiteId(), mComment.commentID, replyText, actionListener);
+ } else {
+ CommentActions.submitReplyToCommentNote(mNote, replyText, actionListener);
+ }
} else {
CommentActions.submitReplyToComment(mLocalBlogId, mComment, replyText, actionListener);
}
@@ -681,44 +737,43 @@ private void updateStatusViews() {
if (!isAdded() || !hasComment())
return;
- final int moderationDrawResId; // drawable resource id for moderation button
- final int moderationTextResId; // string resource id for moderation button
final CommentStatus newStatus; // status to apply when moderation button is tapped
final int statusTextResId; // string resource id for status text
final int statusColor; // color for status text
switch (mComment.getStatusEnum()) {
case APPROVED:
- moderationDrawResId = R.drawable.ic_cab_unapprove;
- moderationTextResId = R.string.mnu_comment_unapprove;
newStatus = CommentStatus.UNAPPROVED;
statusTextResId = R.string.comment_status_approved;
- statusColor = getActivity().getResources().getColor(R.color.comment_status_approved);
+ statusColor = getActivity().getResources().getColor(R.color.calypso_orange_dark);
break;
case UNAPPROVED:
- moderationDrawResId = R.drawable.ic_cab_approve;
- moderationTextResId = R.string.mnu_comment_approve;
newStatus = CommentStatus.APPROVED;
statusTextResId = R.string.comment_status_unapproved;
- statusColor = getActivity().getResources().getColor(R.color.comment_status_unapproved);
+ statusColor = getActivity().getResources().getColor(R.color.calypso_orange_dark);
break;
case SPAM:
- moderationDrawResId = R.drawable.ic_cab_approve;
- moderationTextResId = R.string.mnu_comment_approve;
newStatus = CommentStatus.APPROVED;
statusTextResId = R.string.comment_status_spam;
statusColor = getActivity().getResources().getColor(R.color.comment_status_spam);
break;
case TRASH:
- // should never get here
- moderationDrawResId = R.drawable.ic_cab_approve;
- moderationTextResId = R.string.mnu_comment_approve;
+ default:
newStatus = CommentStatus.APPROVED;
statusTextResId = R.string.comment_status_trash;
statusColor = getActivity().getResources().getColor(R.color.comment_status_spam);
break;
- default:
- return;
+ }
+
+ int commentActionCount = 0;
+
+ if (mNote != null && canLike()) {
+ commentActionCount++;
+ mBtnLikeComment.setVisibility(View.VISIBLE);
+
+ toggleLikeButton(mNote.hasLikedComment());
+ } else {
+ mBtnLikeComment.setVisibility(View.GONE);
}
// comment status is only shown if this comment is from one of this user's blogs and the
@@ -735,11 +790,14 @@ private void updateStatusViews() {
}
if (canModerate()) {
- setTextDrawable(mBtnModerateComment, moderationDrawResId);
- mBtnModerateComment.setText(moderationTextResId);
+ commentActionCount++;
+ setModerateButtonForStatus(mComment.getStatusEnum());
mBtnModerateComment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
+ mComment.setStatus(newStatus.toString());
+ setModerateButtonForStatus(newStatus);
+ AniUtils.startAnimation(mBtnModerateIcon, R.anim.notifications_button_scale);
moderateComment(newStatus);
}
});
@@ -749,6 +807,7 @@ public void onClick(View v) {
}
if (canMarkAsSpam()) {
+ commentActionCount++;
mBtnSpamComment.setVisibility(View.VISIBLE);
if (mComment.getStatusEnum() == CommentStatus.SPAM) {
mBtnSpamComment.setText(R.string.mnu_comment_unspam);
@@ -759,13 +818,47 @@ public void onClick(View v) {
mBtnSpamComment.setVisibility(View.GONE);
}
- mBtnTrashComment.setVisibility(canTrash() ? View.VISIBLE : View.GONE);
- mBtnEditComment.setVisibility(canEdit() ? View.VISIBLE : View.GONE);
+ if (canTrash()) {
+ commentActionCount++;
+ mBtnTrashComment.setVisibility(View.VISIBLE);
+ } else {
+ mBtnTrashComment.setVisibility(View.GONE);
+ }
- // animate the buttons in if they're not visible
- boolean isAnimating = (mLayoutButtons.getAnimation() != null && !mLayoutButtons.getAnimation().hasEnded());
- if ((mLayoutButtons.getVisibility() != View.VISIBLE || isAnimating) && (canMarkAsSpam() || canModerate())) {
- AniUtils.flyIn(mLayoutButtons);
+ if (canEdit()) {
+ commentActionCount++;
+ mBtnEditComment.setVisibility(View.VISIBLE);
+ } else {
+ mBtnEditComment.setVisibility(View.GONE);
+ }
+
+ if (commentActionCount > 3) {
+ // Hide buttons that will show in the overflow menu
+ if (commentActionCount >= 4 && canEdit()) {
+ mBtnEditComment.setVisibility(View.GONE);
+ }
+
+ if (commentActionCount == 5 && canMarkAsSpam()) {
+ mBtnSpamComment.setVisibility(View.GONE);
+ }
+
+ mBtnMore.setVisibility(View.VISIBLE);
+ } else {
+ mBtnMore.setVisibility(View.GONE);
+ }
+
+ mLayoutButtons.setVisibility(View.VISIBLE);
+ }
+
+ private void setModerateButtonForStatus(CommentStatus status) {
+ if (status == CommentStatus.APPROVED) {
+ mBtnModerateIcon.setImageResource(R.drawable.ic_action_approve_active);
+ mBtnModerateTextView.setText(R.string.comment_status_approved);
+ mBtnModerateTextView.setTextColor(getActivity().getResources().getColor(R.color.calypso_orange_dark));
+ } else {
+ mBtnModerateIcon.setImageResource(R.drawable.ic_action_approve);
+ mBtnModerateTextView.setText(R.string.mnu_comment_approve);
+ mBtnModerateTextView.setTextColor(getActivity().getResources().getColor(R.color.calypso_blue));
}
}
@@ -790,33 +883,133 @@ private boolean canTrash() {
private boolean canEdit() {
return (mLocalBlogId > 0 && canModerate());
}
+ private boolean canLike() {
+ return (!mShouldRequestCommentFromNote && mEnabledActions != null && mEnabledActions.contains(EnabledActions.ACTION_LIKE));
+ }
/*
* display the comment associated with the passed notification
*/
private void showCommentForNote(Note note) {
+ if (getView() == null) return;
+ View view = getView();
+
+ // hide standard comment views, since we'll be adding note blocks instead
+ View commentContent = view.findViewById(R.id.comment_content);
+ if (commentContent != null) {
+ commentContent.setVisibility(View.GONE);
+ }
+
+ View commentText = view.findViewById(R.id.text_content);
+ if (commentText != null) {
+ commentText.setVisibility(View.GONE);
+ }
+
+ // Now we'll add a detail fragment list
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+ mNotificationsDetailListFragment = NotificationsDetailListFragment.newInstance(note);
+ mNotificationsDetailListFragment.setFooterView(mLayoutButtons);
+ fragmentTransaction.add(R.id.comment_content_container, mNotificationsDetailListFragment);
+ fragmentTransaction.commitAllowingStateLoss();
+
/*
* determine which actions to enable for this comment - if the comment is from this user's
* blog then all actions will be enabled, but they won't be if it's a reply to a comment
* this user made on someone else's blog
*/
mEnabledActions = note.getEnabledActions();
+ mRemoteBlogId = note.getSiteId();
- mRemoteBlogId = note.getBlogId();
- long commentId = note.getCommentId();
+ // Set 'Reply to (Name)' in comment reply EditText if it's a reasonable size
+ if (!TextUtils.isEmpty(mNote.getCommentAuthorName()) && mNote.getCommentAuthorName().length() < 28) {
+ mEditReply.setHint(String.format(getString(R.string.comment_reply_to_user), mNote.getCommentAuthorName()));
+ }
// note that the local blog id won't be found if the comment is from someone else's blog
int localBlogId = WordPress.wpDB.getLocalTableBlogIdForRemoteBlogId(mRemoteBlogId);
- // first try to get from local db, if that fails request it from the server
- final Comment comment = (localBlogId > 0 ? CommentTable.getComment(localBlogId, commentId) : null);
- if (comment != null) {
- setComment(localBlogId, comment);
+ setComment(localBlogId, note.buildComment());
+ }
+
+ // Like or unlike a comment via the REST API
+ private void likeComment() {
+ if (mNote == null) return;
+
+ toggleLikeButton(!mBtnLikeComment.isActivated());
+
+ ReaderAnim.animateLikeButton(mBtnLikeIcon, mBtnLikeComment.isActivated());
+
+ boolean commentWasUnapproved = false;
+ if (mNotificationsDetailListFragment != null && mComment != null) {
+ // Optimistically set comment to approved when liking an unapproved comment
+ // WP.com will set a comment to approved if it is liked while unapproved
+ if (mBtnLikeComment.isActivated() && mComment.getStatusEnum() == CommentStatus.UNAPPROVED) {
+ mComment.setStatus(CommentStatus.toString(CommentStatus.APPROVED));
+ mNotificationsDetailListFragment.refreshBlocksForCommentStatus(CommentStatus.APPROVED);
+ setModerateButtonForStatus(CommentStatus.APPROVED);
+ commentWasUnapproved = true;
+ }
+ }
+
+ final boolean commentStatusShouldRevert = commentWasUnapproved;
+ WordPress.getRestClientUtils().likeComment(String.valueOf(mNote.getSiteId()),
+ String.valueOf(mNote.getCommentId()),
+ mBtnLikeComment.isActivated(),
+ new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject response) {
+ if (response != null && !response.optBoolean("success")) {
+ if (!isAdded()) return;
+
+ // Failed, so switch the button state back
+ toggleLikeButton(!mBtnLikeComment.isActivated());
+
+ if (commentStatusShouldRevert) {
+ setCommentStatusUnapproved();
+ }
+ }
+ }
+ }, new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ if (!isAdded()) return;
+
+ toggleLikeButton(!mBtnLikeComment.isActivated());
+
+ if (commentStatusShouldRevert) {
+ setCommentStatusUnapproved();
+ }
+ }
+ });
+ }
+
+ private void setCommentStatusUnapproved() {
+ mComment.setStatus(CommentStatus.toString(CommentStatus.UNAPPROVED));
+ mNotificationsDetailListFragment.refreshBlocksForCommentStatus(CommentStatus.UNAPPROVED);
+ setModerateButtonForStatus(CommentStatus.UNAPPROVED);
+ }
+
+ private void toggleLikeButton(boolean isLiked) {
+ if (isLiked) {
+ mBtnLikeTextView.setText(getResources().getString(R.string.mnu_comment_liked));
+ mBtnLikeTextView.setTextColor(getResources().getColor(R.color.orange_medium));
+ mBtnLikeIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_action_like_active));
+ mBtnLikeComment.setActivated(true);
} else {
- requestComment(localBlogId, mRemoteBlogId, commentId);
+ mBtnLikeTextView.setText(getResources().getString(R.string.reader_label_like));
+ mBtnLikeTextView.setTextColor(getResources().getColor(R.color.calypso_blue));
+ mBtnLikeIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_action_like));
+ mBtnLikeComment.setActivated(false);
}
}
+ public String getReplyText() {
+ if (mEditReply == null) return null;
+
+ return mEditReply.getText().toString();
+ }
+
/*
* request a comment - note that this uses the REST API rather than XMLRPC, which means the user must
* either be wp.com or have Jetpack, but it's safe to do this since this method is only called when
@@ -825,22 +1018,26 @@ private void showCommentForNote(Note note) {
private void requestComment(final int localBlogId,
final int remoteBlogId,
final long commentId) {
- final ProgressBar progress = (isAdded() ? (ProgressBar) getView().findViewById(R.id.progress_loading) : null);
- if (progress != null)
+
+ final ProgressBar progress = (isAdded() && getView() != null ?
+ (ProgressBar) getView().findViewById(R.id.progress_loading) : null);
+ if (progress != null) {
progress.setVisibility(View.VISIBLE);
+ }
RestRequest.Listener restListener = new RestRequest.Listener() {
@Override
public void onResponse(JSONObject jsonObject) {
- mIsRequestingComment = false;
if (isAdded()) {
- if (progress != null)
+ if (progress != null) {
progress.setVisibility(View.GONE);
+ }
Comment comment = Comment.fromJSON(jsonObject);
if (comment != null) {
// save comment to local db if localBlogId is valid
- if (localBlogId > 0)
+ if (localBlogId > 0) {
CommentTable.addComment(localBlogId, comment);
+ }
// now, at long last, show the comment
setComment(localBlogId, comment);
}
@@ -850,18 +1047,17 @@ public void onResponse(JSONObject jsonObject) {
RestRequest.ErrorListener restErrListener = new RestRequest.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
- mIsRequestingComment = false;
AppLog.e(T.COMMENTS, VolleyUtils.errStringFromVolleyError(volleyError), volleyError);
if (isAdded()) {
- if (progress != null)
+ if (progress != null) {
progress.setVisibility(View.GONE);
+ }
ToastUtils.showToast(getActivity(), R.string.reader_toast_err_get_comment, ToastUtils.Duration.LONG);
}
}
};
final String path = String.format("/sites/%s/comments/%s", remoteBlogId, commentId);
- mIsRequestingComment = true;
WordPress.getRestClientUtils().get(path, restListener, restErrListener);
}
}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentUtils.java
index f4f4a1afe338..e6242e73c240 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentUtils.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentUtils.java
@@ -1,7 +1,13 @@
package org.wordpress.android.ui.comments;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.drawable.Drawable;
+import android.text.Layout;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.Spanned;
+import android.text.style.LeadingMarginSpan;
import android.text.util.Linkify;
import android.widget.TextView;
@@ -15,22 +21,25 @@ public class CommentUtils {
/*
* displays comment text as html, including retrieving images
*/
- public static void displayHtmlComment(TextView textView, String content, int maxImageSize) {
+ public static CharSequence displayHtmlComment(TextView textView, String content, int maxImageSize) {
if (textView == null)
- return;
+ return null;
if (content == null) {
textView.setText(null);
- return;
+ return null;
}
// skip performance hit of html conversion if content doesn't contain html
if (!content.contains("<") && !content.contains("&")) {
- textView.setText(content.trim());
+ content = content.trim();
+ textView.setText(content);
// make sure unnamed links are clickable
- if (content.contains("://"))
+ if (content.contains("://")) {
Linkify.addLinks(textView, Linkify.WEB_URLS);
- return;
+ }
+
+ return content;
}
// convert emoticons first (otherwise they'll be downloaded)
@@ -58,6 +67,43 @@ public static void displayHtmlComment(TextView textView, String content, int max
end--;
}
- textView.setText(source.subSequence(start, end));
+ textView.setText(source);
+
+ return source;
+ }
+
+ // Assumes all lines after first line will not be indented
+ public static void indentTextViewFirstLine(TextView textView, Spannable text, int textOffsetX) {
+ if (text != null && textOffsetX > 0) {
+ text.setSpan(new TextWrappingLeadingMarginSpan(1, textOffsetX), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ textView.setText(text);
+ } else {
+ textView.setText(text);
+ }
+ }
+
+ private static class TextWrappingLeadingMarginSpan implements LeadingMarginSpan.LeadingMarginSpan2 {
+ private int margin;
+ private int lines;
+
+ public TextWrappingLeadingMarginSpan(int lines, int margin) {
+ this.margin = margin;
+ this.lines = lines;
+ }
+
+ @Override
+ public int getLeadingMargin(boolean first) {
+ return first ? margin : 0;
+ }
+
+ @Override
+ public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
+
+ }
+
+ @Override
+ public int getLeadingMarginLineCount() {
+ return lines;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentsActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentsActivity.java
index fe0f0741de92..0183f90eef02 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentsActivity.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentsActivity.java
@@ -7,48 +7,50 @@
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Parcelable;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.View;
+
+import com.cocosw.undobar.UndoBarController;
import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.models.BlogPairId;
+import org.wordpress.android.models.Comment;
+import org.wordpress.android.models.CommentStatus;
import org.wordpress.android.models.Note;
import org.wordpress.android.ui.WPActionBarActivity;
import org.wordpress.android.ui.comments.CommentsListFragment.OnCommentSelectedListener;
import org.wordpress.android.ui.notifications.NotificationFragment;
import org.wordpress.android.ui.reader.ReaderPostDetailFragment;
import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.ToastUtils;
+
+import javax.annotation.Nonnull;
public class CommentsActivity extends WPActionBarActivity
implements OnCommentSelectedListener,
NotificationFragment.OnPostClickListener,
+ CommentActions.OnCommentActionListener,
CommentActions.OnCommentChangeListener {
- private static final String KEY_HIGHLIGHTED_COMMENT_ID = "highlighted_comment_id";
private static final String KEY_SELECTED_COMMENT_ID = "selected_comment_id";
private static final String KEY_SELECTED_POST_ID = "selected_post_id";
static final String KEY_AUTO_REFRESHED = "has_auto_refreshed";
- private boolean mDualPane;
private long mSelectedCommentId;
- private boolean mCommentSelected;
- private BlogPairId mSelectedReaderPost;
- private BlogPairId mTmpSelectedReaderPost;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
createMenuDrawer(R.layout.comment_activity);
- View detailView = findViewById(R.id.fragment_comment_detail);
- mDualPane = detailView != null && detailView.getVisibility() == View.VISIBLE;
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(true);
}
+
+ getFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
+
setTitle(getString(R.string.tab_comments));
- FragmentManager fm = getFragmentManager();
- fm.addOnBackStackChangedListener(mOnBackStackChangedListener);
restoreSavedInstance(savedInstanceState);
}
@@ -56,23 +58,10 @@ private void restoreSavedInstance(Bundle savedInstanceState) {
if (savedInstanceState != null) {
getIntent().putExtra(KEY_AUTO_REFRESHED, savedInstanceState.getBoolean(KEY_AUTO_REFRESHED));
- // restore the highlighted comment
- long commentId = savedInstanceState.getLong(KEY_HIGHLIGHTED_COMMENT_ID);
- if (commentId != 0) {
- if (hasListFragment()) {
- // on dual pane mode, the highlighted comment is also selected
- getListFragment().setHighlightedCommentId(commentId);
- }
- if (mDualPane) {
- onCommentSelected(commentId);
- }
- }
// restore the selected comment
- if (!mDualPane) {
- commentId = savedInstanceState.getLong(KEY_SELECTED_COMMENT_ID);
- if (commentId != 0) {
- onCommentSelected(commentId);
- }
+ long commentId = savedInstanceState.getLong(KEY_SELECTED_COMMENT_ID);
+ if (commentId != 0) {
+ onCommentSelected(commentId);
}
// restore the post detail fragment if one was selected
BlogPairId selectedPostId = (BlogPairId) savedInstanceState.get(KEY_SELECTED_POST_ID);
@@ -82,21 +71,6 @@ private void restoreSavedInstance(Bundle savedInstanceState) {
}
}
- /**
- * Called by CommentAdapter after comment adapter first load
- */
- public void commentAdapterFirstLoad() {
- // if dual pane mode and no selected comments, select the first comment in the list
- if (mDualPane && !mCommentSelected && hasListFragment()) {
- long firstCommentId = getListFragment().getFirstCommentId();
- onCommentSelected(firstCommentId);
- }
- // used to scroll the list view after it has been created
- if (mSelectedCommentId != 0 && hasListFragment()) {
- getListFragment().setHighlightedCommentId(mSelectedCommentId);
- }
- }
-
@Override
public void onBlogChanged() {
super.onBlogChanged();
@@ -131,15 +105,10 @@ public boolean onCreateOptionsMenu(Menu menu) {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
- if (mDualPane) {
- // let WPActionBarActivity handle it (toggles menu drawer)
- return super.onOptionsItemSelected(item);
- } else {
FragmentManager fm = getFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
- return true;
- }
+ return true;
}
break;
}
@@ -150,34 +119,10 @@ public boolean onOptionsItemSelected(final MenuItem item) {
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
int backStackEntryCount = getFragmentManager().getBackStackEntryCount();
- // This is ugly, but onBackStackChanged is not called just after a fragment commit.
- // In a 2 commits in a row case, onBackStackChanged is called twice but after the
- // 2 commits. That's why mSelectedReaderPost can't be affected correctly after the first commit.
- switch (backStackEntryCount) {
- case 2:
- // 2 entries means we're showing the associated post in the reader detail fragment
- // (can't happen in dual pane mode)
- mSelectedReaderPost = mTmpSelectedReaderPost;
- break;
- case 1:
- // In dual pane mode, 1 entry means:
- // we're showing the associated post in the reader detail fragment
- // In single pane mode, 1 entry means:
- // we're showing the comment fragment on top of comment list
- if (mDualPane) {
- mSelectedReaderPost = mTmpSelectedReaderPost;
- } else {
- mSelectedReaderPost = null;
- }
- break;
- case 0:
- if (!mDualPane) {
- getListFragment().setHighlightedCommentId(-1);
- }
- mMenuDrawer.setDrawerIndicatorEnabled(true);
- mSelectedCommentId = 0;
- mSelectedReaderPost = null;
- break;
+ if (backStackEntryCount == 0) {
+ mMenuDrawer.setDrawerIndicatorEnabled(true);
+ } else {
+ mMenuDrawer.setDrawerIndicatorEnabled(false);
}
}
};
@@ -188,38 +133,6 @@ protected void onNewIntent(Intent intent) {
AppLog.d(AppLog.T.COMMENTS, "comment activity new intent");
}
- /*
- * called from comment list & comment detail when comments are moderated/replied/trashed
- */
- @Override
- public void onCommentChanged(CommentActions.ChangedFrom changedFrom, CommentActions.ChangeType changeType) {
- // update the comment counter on the menu drawer
- updateMenuDrawer();
-
- switch (changedFrom) {
- case COMMENT_LIST:
- reloadCommentDetail();
- break;
- case COMMENT_DETAIL:
- switch (changeType) {
- case TRASHED:
- updateCommentList();
- // remove the detail view since comment was deleted
- FragmentManager fm = getFragmentManager();
- if (fm.getBackStackEntryCount() > 0) {
- fm.popBackStack();
- }
- break;
- case REPLIED:
- updateCommentList();
- break;
- default:
- reloadCommentList();
- break;
- }
- break;
- }
- }
private CommentDetailFragment getDetailFragment() {
Fragment fragment = getFragmentManager().findFragmentByTag(getString(
@@ -247,19 +160,7 @@ private boolean hasListFragment() {
return (getListFragment() != null);
}
- private ReaderPostDetailFragment getReaderFragment() {
- Fragment fragment = getFragmentManager().findFragmentByTag(getString(R.string.fragment_tag_reader_post_detail));
- if (fragment == null)
- return null;
- return (ReaderPostDetailFragment)fragment;
- }
-
- private boolean hasReaderFragment() {
- return (getReaderFragment() != null);
- }
-
void showReaderFragment(long remoteBlogId, long postId) {
- mTmpSelectedReaderPost = new BlogPairId(remoteBlogId, postId);
FragmentManager fm = getFragmentManager();
fm.executePendingTransactions();
@@ -285,34 +186,19 @@ public void onCommentSelected(long commentId) {
return;
}
fm.executePendingTransactions();
- mCommentSelected = true;
- CommentDetailFragment detailFragment = getDetailFragment();
CommentsListFragment listFragment = getListFragment();
- if (mDualPane) {
- // dual pane mode with list/detail side-by-side - remove the reader fragment if it exists,
- // then show this comment in the detail view and highlight it in the list view
- if (hasReaderFragment()) {
- fm.popBackStackImmediate();
- }
- detailFragment.setComment(WordPress.getCurrentLocalTableBlogId(), commentId);
- if (listFragment != null) {
- listFragment.setHighlightedCommentId(commentId);
- }
- } else {
- FragmentTransaction ft = fm.beginTransaction();
- String tagForFragment = getString(R.string.fragment_tag_comment_detail);
- detailFragment = CommentDetailFragment.newInstance(WordPress.getCurrentLocalTableBlogId(),
- commentId);
- ft.add(R.id.layout_fragment_container, detailFragment, tagForFragment).addToBackStack(tagForFragment)
- .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- if (listFragment != null) {
- listFragment.setHighlightedCommentId(commentId);
- ft.hide(listFragment);
- }
- ft.commitAllowingStateLoss();
- mMenuDrawer.setDrawerIndicatorEnabled(false);
+ FragmentTransaction ft = fm.beginTransaction();
+ String tagForFragment = getString(R.string.fragment_tag_comment_detail);
+ CommentDetailFragment detailFragment = CommentDetailFragment.newInstance(WordPress.getCurrentLocalTableBlogId(),
+ commentId);
+ ft.add(R.id.layout_fragment_container, detailFragment, tagForFragment).addToBackStack(tagForFragment)
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
+ if (listFragment != null) {
+ ft.hide(listFragment);
}
+ ft.commitAllowingStateLoss();
+ mMenuDrawer.setDrawerIndicatorEnabled(false);
}
/*
@@ -324,15 +210,6 @@ public void onPostClicked(Note note, int remoteBlogId, int postId) {
showReaderFragment(remoteBlogId, postId);
}
- /*
- * reload the comment in the detail view if it's showing
- */
- private void reloadCommentDetail() {
- CommentDetailFragment detailFragment = getDetailFragment();
- if (detailFragment != null)
- detailFragment.reloadComment();
- }
-
/*
* reload the comment list from existing data
*/
@@ -354,7 +231,7 @@ void updateCommentList() {
}
@Override
- public void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(@Nonnull Bundle outState) {
// https://code.google.com/p/android/issues/detail?id=19917
if (outState.isEmpty()) {
outState.putBoolean("bug_19917_fix", true);
@@ -364,14 +241,8 @@ public void onSaveInstanceState(Bundle outState) {
if (mSelectedCommentId != 0) {
outState.putLong(KEY_SELECTED_COMMENT_ID, mSelectedCommentId);
}
- if (mSelectedReaderPost != null) {
- outState.putSerializable(KEY_SELECTED_POST_ID, mSelectedReaderPost);
- }
+
if (hasListFragment()) {
- long commentId = getListFragment().getHighlightedCommentId();
- if (commentId != 0) {
- outState.putLong(KEY_HIGHLIGHTED_COMMENT_ID, commentId);
- }
outState.putBoolean(KEY_AUTO_REFRESHED, getListFragment().mHasAutoRefreshedComments);
}
super.onSaveInstanceState(outState);
@@ -384,4 +255,90 @@ protected Dialog onCreateDialog(int id) {
return dialog;
return super.onCreateDialog(id);
}
+
+ @Override
+ public void onModerateComment(final int accountId, final Comment comment,
+ final CommentStatus newStatus) {
+ FragmentManager fm = getFragmentManager();
+ if (fm.getBackStackEntryCount() > 0) {
+ fm.popBackStack();
+ }
+
+ if (newStatus == CommentStatus.APPROVED || newStatus == CommentStatus.UNAPPROVED) {
+ getListFragment().setCommentIsModerating(comment.commentID, true);
+ CommentActions.moderateComment(accountId, comment, newStatus,
+ new CommentActions.CommentActionListener() {
+ @Override
+ public void onActionResult(boolean succeeded) {
+ if (isFinishing()) return;
+
+ getListFragment().setCommentIsModerating(comment.commentID, false);
+
+ if (succeeded) {
+ updateMenuDrawer();
+ getListFragment().updateComments(false);
+ } else {
+ ToastUtils.showToast(CommentsActivity.this,
+ R.string.error_moderate_comment,
+ ToastUtils.Duration.LONG
+ );
+ }
+ }
+ });
+ } else if (newStatus == CommentStatus.SPAM || newStatus == CommentStatus.TRASH) {
+ // Remove comment from comments list
+ getListFragment().removeComment(comment);
+ getListFragment().setCommentIsModerating(comment.commentID, true);
+
+ new UndoBarController.UndoBar(this)
+ .message(newStatus == CommentStatus.TRASH ? R.string.comment_trashed : R.string.comment_spammed)
+ .listener(new UndoBarController.AdvancedUndoListener() {
+ @Override
+ public void onHide(Parcelable parcelable) {
+ CommentActions.moderateComment(accountId, comment, newStatus,
+ new CommentActions.CommentActionListener() {
+ @Override
+ public void onActionResult(boolean succeeded) {
+ if (isFinishing()) return;
+
+ getListFragment().setCommentIsModerating(comment.commentID, false);
+
+ if (!succeeded) {
+ // show comment again upon error
+ getListFragment().loadComments();
+ ToastUtils.showToast(CommentsActivity.this,
+ R.string.error_moderate_comment,
+ ToastUtils.Duration.LONG
+ );
+ } else {
+ updateMenuDrawer();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onClear() {
+ //noop
+ }
+
+ @Override
+ public void onUndo(Parcelable parcelable) {
+ getListFragment().setCommentIsModerating(comment.commentID, false);
+ // On undo load from the db to show the comment again
+ getListFragment().loadComments();
+ }
+ }).show();
+ }
+
+
+ }
+
+ @Override
+ public void onCommentChanged(CommentActions.ChangedFrom changedFrom, CommentActions.ChangeType changeType) {
+ if (changedFrom == CommentActions.ChangedFrom.COMMENT_DETAIL
+ && changeType == CommentActions.ChangeType.EDITED) {
+ reloadCommentList();
+ }
+ }
}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentsListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentsListFragment.java
index d087d7f2f538..f5367020fb29 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentsListFragment.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/comments/CommentsListFragment.java
@@ -58,10 +58,8 @@ public class CommentsListFragment extends Fragment {
private UpdateCommentsTask mUpdateCommentsTask;
private OnCommentSelectedListener mOnCommentSelectedListener;
- private OnCommentChangeListener mOnCommentChangeListener;
private static final int COMMENTS_PER_PAGE = 30;
- private boolean mFirstLoad = true;
private ListView getListView() {
return mListView;
@@ -82,12 +80,6 @@ public void onDataLoaded(boolean isEmpty) {
} else {
hideEmptyView();
}
- if (mFirstLoad) {
- mFirstLoad = false;
- if (getActivity() != null && getActivity() instanceof CommentsActivity) {
- ((CommentsActivity) getActivity()).commentAdapterFirstLoad();
- }
- }
}
};
@@ -139,11 +131,10 @@ void clear() {
}
}
- public long getFirstCommentId() {
- if (getCommentAdapter() != null && getCommentAdapter().getCount() > 0) {
- return ((Comment) getCommentAdapter().getItem(0)).commentID;
+ public void removeComment(Comment comment) {
+ if (hasCommentAdapter() && comment != null) {
+ getCommentAdapter().removeComment(comment);
}
- return 0;
}
@Override
@@ -169,7 +160,6 @@ public void onAttach(Activity activity) {
try {
// check that the containing activity implements our callback
mOnCommentSelectedListener = (OnCommentSelectedListener) activity;
- mOnCommentChangeListener = (OnCommentChangeListener) activity;
} catch (ClassCastException e) {
activity.finish();
throw new ClassCastException(activity.toString() + " must implement Callback");
@@ -269,10 +259,6 @@ public void onCommentsModerated(final CommentList moderatedComments) {
if (moderatedComments.size() > 0) {
getCommentAdapter().clearSelectedComments();
getCommentAdapter().replaceComments(moderatedComments);
- if (mOnCommentChangeListener != null) {
- ChangeType changeType = (newStatus == CommentStatus.TRASH ? ChangeType.TRASHED : ChangeType.STATUS);
- mOnCommentChangeListener.onCommentChanged(ChangedFrom.COMMENT_LIST, changeType);
- }
} else {
ToastUtils.showToast(getActivity(), R.string.error_moderate_comment);
}
@@ -319,8 +305,6 @@ public void onCommentsModerated(final CommentList deletedComments) {
if (deletedComments.size() > 0) {
getCommentAdapter().clearSelectedComments();
getCommentAdapter().deleteComments(deletedComments);
- if (mOnCommentChangeListener != null)
- mOnCommentChangeListener.onCommentChanged(ChangedFrom.COMMENT_LIST, ChangeType.TRASHED);
} else {
ToastUtils.showToast(getActivity(), R.string.error_moderate_comment);
}
@@ -331,17 +315,6 @@ public void onCommentsModerated(final CommentList deletedComments) {
listener);
}
- long getHighlightedCommentId() {
- return (hasCommentAdapter() ? getCommentAdapter().getHighlightedCommentId() : 0);
- }
- void setHighlightedCommentId(long commentId) {
- getCommentAdapter().setHighlightedCommentId(commentId);
- int position = getCommentAdapter().indexOfCommentId(commentId);
- if (position != -1) {
- getListView().setSelection(position);
- }
- }
-
private void setUpListView() {
ListView listView = this.getListView();
listView.setAdapter(getCommentAdapter());
@@ -350,8 +323,10 @@ private void setUpListView() {
public void onItemClick(AdapterView> parent, View view, int position, long id) {
if (mActionMode == null) {
Comment comment = (Comment) getCommentAdapter().getItem(position);
- mOnCommentSelectedListener.onCommentSelected(comment.commentID);
- getListView().invalidateViews();
+ if (!getCommentAdapter().isModeratingCommentId(comment.commentID)) {
+ mOnCommentSelectedListener.onCommentSelected(comment.commentID);
+ getListView().invalidateViews();
+ }
} else {
getCommentAdapter().toggleItemSelected(position, view);
}
@@ -397,6 +372,16 @@ void updateComments(boolean loadMore) {
mUpdateCommentsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
+ public void setCommentIsModerating(long commentId, boolean isModerating) {
+ if (!hasCommentAdapter()) return;
+
+ if (isModerating) {
+ getCommentAdapter().addModeratingCommentId(commentId);
+ } else {
+ getCommentAdapter().removeModeratingCommentId(commentId);
+ }
+ }
+
/*
* task to retrieve latest comments from server
*/
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/comments/EditCommentActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/comments/EditCommentActivity.java
index 2dbcfe3c3fb2..96bbbbe354b1 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/comments/EditCommentActivity.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/comments/EditCommentActivity.java
@@ -14,18 +14,29 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import com.android.volley.VolleyError;
+import com.simperium.client.BucketObjectMissingException;
+import com.wordpress.rest.RestRequest;
+
+import org.json.JSONObject;
import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.datasets.CommentTable;
import org.wordpress.android.models.Blog;
import org.wordpress.android.models.Comment;
import org.wordpress.android.models.CommentStatus;
+import org.wordpress.android.models.Note;
+import org.wordpress.android.ui.notifications.utils.SimperiumUtils;
import org.wordpress.android.util.NetworkUtils;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.EditTextUtils;
import org.wordpress.android.util.ToastUtils;
+import org.wordpress.android.util.VolleyUtils;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlrpc.android.XMLRPCClientInterface;
import org.xmlrpc.android.XMLRPCException;
@@ -38,12 +49,14 @@
public class EditCommentActivity extends Activity {
static final String ARG_LOCAL_BLOG_ID = "blog_id";
static final String ARG_COMMENT_ID = "comment_id";
+ static final String ARG_NOTE_ID = "note_id";
private static final int ID_DIALOG_SAVING = 0;
private int mLocalBlogId;
private long mCommentId;
private Comment mComment;
+ private Note mNote;
@Override
public void onCreate(Bundle icicle) {
@@ -58,22 +71,44 @@ public void onCreate(Bundle icicle) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
- if (!loadComment(getIntent())) {
- ToastUtils.showToast(this, R.string.error_load_comment);
- finish();
- }
+ loadComment(getIntent());
}
- private boolean loadComment(Intent intent) {
- if (intent == null)
- return false;
+ private void loadComment(Intent intent) {
+ if (intent == null) {
+ showErrorAndFinish();
+ return;
+ }
mLocalBlogId = intent.getIntExtra(ARG_LOCAL_BLOG_ID, 0);
mCommentId = intent.getLongExtra(ARG_COMMENT_ID, 0);
- mComment = CommentTable.getComment(mLocalBlogId, mCommentId);
- if (mComment == null)
- return false;
+ final String noteId = intent.getStringExtra(ARG_NOTE_ID);
+ if (noteId == null) {
+ mComment = CommentTable.getComment(mLocalBlogId, mCommentId);
+ if (mComment == null) {
+ showErrorAndFinish();
+ return;
+ }
+ configureViews();
+ } else {
+ if (SimperiumUtils.getNotesBucket() != null) {
+ try {
+ mNote = SimperiumUtils.getNotesBucket().get(noteId);
+ requestFullCommentForNote(mNote);
+ } catch (BucketObjectMissingException e) {
+ showErrorAndFinish();
+ }
+ }
+ }
+ }
+
+ private void showErrorAndFinish() {
+ ToastUtils.showToast(this, R.string.error_load_comment);
+ finish();
+ }
+
+ private void configureViews() {
final EditText editAuthorName = (EditText) this.findViewById(R.id.author_name);
editAuthorName.setText(mComment.getAuthorName());
@@ -83,7 +118,14 @@ private boolean loadComment(Intent intent) {
final EditText editAuthorUrl = (EditText) this.findViewById(R.id.author_url);
editAuthorUrl.setText(mComment.getAuthorUrl());
- final EditText editContent = (EditText) this.findViewById(R.id.comment_content);
+ // REST API can currently only edit comment content
+ if (mNote != null) {
+ editAuthorName.setVisibility(View.GONE);
+ editAuthorEmail.setVisibility(View.GONE);
+ editAuthorUrl.setVisibility(View.GONE);
+ }
+
+ final EditText editContent = (EditText) this.findViewById(R.id.edit_comment_content);
editContent.setText(mComment.getCommentText());
// show error when comment content is empty
@@ -105,8 +147,6 @@ public void afterTextChanged(Editable s) {
}
}
});
-
- return true;
}
@Override
@@ -138,7 +178,7 @@ private String getEditTextStr(int resId) {
private void saveComment() {
// make sure comment content was entered
- final EditText editContent = (EditText) findViewById(R.id.comment_content);
+ final EditText editContent = (EditText) findViewById(R.id.edit_comment_content);
if (EditTextUtils.isEmpty(editContent)) {
editContent.setError(getString(R.string.content_required));
return;
@@ -154,9 +194,36 @@ private void saveComment() {
if (!NetworkUtils.checkConnection(this))
return;
- if (mIsUpdateTaskRunning)
- AppLog.w(AppLog.T.COMMENTS, "update task already running");
- new UpdateCommentTask().execute();
+ if (mNote != null) {
+ // Edit comment via REST API :)
+ showSaveDialog();
+ WordPress.getRestClientUtils().editCommentContent(mNote.getSiteId(),
+ mNote.getCommentId(),
+ EditTextUtils.getText(editContent),
+ new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject response) {
+ if (isFinishing()) return;
+
+ dismissSaveDialog();
+ setResult(RESULT_OK);
+ finish();
+ }
+ }, new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ if (isFinishing()) return;
+
+ dismissSaveDialog();
+ showEditErrorAlert();
+ }
+ });
+ } else {
+ // Edit comment via XML-RPC :(
+ if (mIsUpdateTaskRunning)
+ AppLog.w(AppLog.T.COMMENTS, "update task already running");
+ new UpdateCommentTask().execute();
+ }
}
/*
@@ -169,7 +236,7 @@ private boolean isCommentEdited() {
final String authorName = getEditTextStr(R.id.author_name);
final String authorEmail = getEditTextStr(R.id.author_email);
final String authorUrl = getEditTextStr(R.id.author_url);
- final String content = getEditTextStr(R.id.comment_content);
+ final String content = getEditTextStr(R.id.edit_comment_content);
return !(authorName.equals(mComment.getAuthorName())
&& authorEmail.equals(mComment.getAuthorEmail())
@@ -227,7 +294,7 @@ protected Boolean doInBackground(Void... params) {
final String authorName = getEditTextStr(R.id.author_name);
final String authorEmail = getEditTextStr(R.id.author_email);
final String authorUrl = getEditTextStr(R.id.author_url);
- final String content = getEditTextStr(R.id.comment_content);
+ final String content = getEditTextStr(R.id.edit_comment_content);
final Map postHash = new HashMap();
@@ -269,6 +336,8 @@ protected Boolean doInBackground(Void... params) {
}
@Override
protected void onPostExecute(Boolean result) {
+ if (isFinishing()) return;
+
mIsUpdateTaskRunning = false;
dismissSaveDialog();
@@ -277,18 +346,66 @@ protected void onPostExecute(Boolean result) {
finish();
} else {
// alert user to error
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(EditCommentActivity.this);
- dialogBuilder.setTitle(getResources().getText(R.string.error));
- dialogBuilder.setMessage(R.string.error_edit_comment);
- dialogBuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- // just close the dialog
- }
- });
- dialogBuilder.setCancelable(true);
- dialogBuilder.create().show();
+ showEditErrorAlert();
+ }
+ }
+ }
+
+ private void showEditErrorAlert() {
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(EditCommentActivity.this);
+ dialogBuilder.setTitle(getResources().getText(R.string.error));
+ dialogBuilder.setMessage(R.string.error_edit_comment);
+ dialogBuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ // just close the dialog
}
+ });
+ dialogBuilder.setCancelable(true);
+ dialogBuilder.create().show();
+ }
+
+ // Request a comment via the REST API for a note
+ private void requestFullCommentForNote(Note note) {
+ if (isFinishing()) return;
+ final ProgressBar progress = (ProgressBar)findViewById(R.id.edit_comment_progress);
+ final View editContainer = findViewById(R.id.edit_comment_container);
+
+ if (progress == null || editContainer == null) {
+ return;
}
+
+ editContainer.setVisibility(View.GONE);
+ progress.setVisibility(View.VISIBLE);
+
+ RestRequest.Listener restListener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (!isFinishing()) {
+ progress.setVisibility(View.GONE);
+ editContainer.setVisibility(View.VISIBLE);
+ Comment comment = Comment.fromJSON(jsonObject);
+ if (comment != null) {
+ mComment = comment;
+ configureViews();
+ } else {
+ showErrorAndFinish();
+ }
+ }
+ }
+ };
+ RestRequest.ErrorListener restErrListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(AppLog.T.COMMENTS, VolleyUtils.errStringFromVolleyError(volleyError), volleyError);
+ if (!isFinishing()) {
+ progress.setVisibility(View.GONE);
+ showErrorAndFinish();
+ }
+ }
+ };
+
+ final String path = String.format("/sites/%s/comments/%s", note.getSiteId(), note.getCommentId());
+ WordPress.getRestClientUtils().get(path, restListener, restErrListener);
}
@Override
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/BigBadgeFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/BigBadgeFragment.java
deleted file mode 100644
index 249e55db1433..000000000000
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/BigBadgeFragment.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.wordpress.android.ui.notifications;
-
-import android.app.Fragment;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.Spanned;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.volley.toolbox.NetworkImageView;
-
-import org.wordpress.android.R;
-import org.wordpress.android.WordPress;
-import org.wordpress.android.models.Note;
-import org.wordpress.android.ui.stats.StatsActivity;
-import org.wordpress.android.ui.stats.StatsWPLinkMovementMethod;
-import org.wordpress.android.util.HtmlUtils;
-import org.wordpress.android.util.JSONUtil;
-
-public class BigBadgeFragment extends Fragment implements NotificationFragment {
- private Note mNote;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle state) {
- View view = inflater.inflate(R.layout.notifications_big_badge, parent, false);
- NetworkImageView badgeImageView = (NetworkImageView) view.findViewById(R.id.badge);
-
- TextView bodyTextView = (TextView) view.findViewById(R.id.body);
- bodyTextView.setMovementMethod(StatsWPLinkMovementMethod.getInstance());
-
- if (getNote() != null) {
- String noteHTML = JSONUtil.queryJSON(getNote().toJSONObject(), "body.html", "");
- if (noteHTML.equals("")) {
- noteHTML = getNote().getSubject();
- }
- Spanned html = HtmlUtils.fromHtml(noteHTML);
- bodyTextView.setText(html);
-
- // Get the badge
- String iconURL = getNote().getIconURL();
- if (!iconURL.equals("")) {
- badgeImageView.setImageUrl(iconURL, WordPress.imageLoader);
- }
-
- // if this is a stats-related note, show stats link and enable tapping badge
- // to view stats - but only if the note is for a blog that's visible
- if (isStatsNote()) {
- final int remoteBlogId = getNote().getMetaValueAsInt("blog_id", -1);
- if (WordPress.wpDB.isDotComAccountVisible(remoteBlogId)) {
- TextView txtStats = (TextView) view.findViewById(R.id.text_stats_link);
- txtStats.setVisibility(View.VISIBLE);
- View.OnClickListener statsListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showStatsActivity(remoteBlogId);
- }
- };
- txtStats.setOnClickListener(statsListener);
- badgeImageView.setOnClickListener(statsListener);
- }
- }
- }
-
- return view;
- }
-
- public void setNote(Note note) {
- mNote = note;
- }
- public Note getNote() {
- return mNote;
- }
-
- /*
- * returns true if this is a stats-related notification - currently handles these types:
- * followed_milestone_achievement
- * post_milestone_achievement
- * like_milestone_achievement
- * traffic_surge
- * best_followed_day_feat
- * best_liked_day_feat
- * most_liked_day
- * most_followed_day
- */
- boolean isStatsNote() {
- if (getNote() == null) {
- return false;
- }
-
- String type = getNote().getType();
- if (type == null) {
- return false;
- }
-
- return (type.contains("_milestone_")
- || type.startsWith("traffic_")
- || type.startsWith("best_")
- || type.startsWith("most_"));
- }
-
- /*
- * show stats for the passed blog
- */
- private void showStatsActivity(int remoteBlogId) {
- if (getActivity() == null || isRemoving()) {
- return;
- }
-
- int localBlogId = WordPress.wpDB.getLocalTableBlogIdForRemoteBlogId(remoteBlogId);
- Intent intent = new Intent(getActivity(), StatsActivity.class);
- intent.putExtra(StatsActivity.ARG_NO_MENU_DRAWER, true);
- intent.putExtra(StatsActivity.ARG_LOCAL_TABLE_BLOG_ID, localBlogId);
- getActivity().startActivity(intent);
- }
-}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/DetailHeader.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/DetailHeader.java
deleted file mode 100644
index ac3f079159f3..000000000000
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/DetailHeader.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * Set a line of text and a URL to open in the browser when clicked
- */
-package org.wordpress.android.ui.notifications;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.wordpress.android.R;
-import org.wordpress.android.models.Note;
-
-public class DetailHeader extends LinearLayout {
- private NotificationFragment.OnPostClickListener mOnPostClickListener;
- private NotificationFragment.OnCommentClickListener mOnCommentClickListener;
-
- public DetailHeader(Context context){
- super(context);
- }
- public DetailHeader(Context context, AttributeSet attributes){
- super(context, attributes);
- }
- public DetailHeader(Context context, AttributeSet attributes, int defStyle){
- super(context, attributes, defStyle);
- }
- TextView getTextView(){
- return (TextView) findViewById(R.id.label);
- }
- public void setText(CharSequence text){
- getTextView().setText(text);
- }
-
- /*
- * set by the owning fragment, calls listener in NotificationsActivity to
- * display the post/comment associated with this notification (if any)
- */
- public void setOnPostClickListener(NotificationFragment.OnPostClickListener listener) {
- mOnPostClickListener = listener;
- }
- public void setOnCommentClickListener(NotificationFragment.OnCommentClickListener listener) {
- mOnCommentClickListener = listener;
- }
-
- /*
- * owning fragment calls this to pass it the note so the post or comment associated with
- * the note can be opened. if there is no associated post or comment, then the passed
- * url is navigated to instead.
- */
- public void setNote(final Note note, final String url) {
- final boolean isComment = (note != null && note.getBlogId() != 0 && note.getPostId() != 0 && note.getCommentId() != 0);
- final boolean isPost = (note != null && note.getBlogId() != 0 && note.getPostId() != 0 && note.getCommentId() == 0);
-
- if (isPost || isComment) {
- setClickable(true);
- setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (isComment && mOnCommentClickListener != null) {
- mOnCommentClickListener.onCommentClicked(note, note.getBlogId(), note.getCommentId());
- } else if (isPost && mOnPostClickListener != null) {
- mOnPostClickListener.onPostClicked(note, note.getBlogId(), note.getPostId());
- }
- }
- });
- } else if (!TextUtils.isEmpty(url)) {
- setClickable(true);
- setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- NotificationsWebViewActivity.openUrl(getContext(), url);
- }
- });
- } else {
- setClickable(false);
- setOnClickListener(null);
- }
- }
-}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/FollowListener.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/FollowListener.java
deleted file mode 100644
index e4ca3a110912..000000000000
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/FollowListener.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.wordpress.android.ui.notifications;
-
-import com.android.volley.VolleyError;
-import com.wordpress.rest.RestRequest.ErrorListener;
-import com.wordpress.rest.RestRequest.Listener;
-
-import org.json.JSONObject;
-import org.wordpress.android.WordPress;
-import org.wordpress.android.util.AppLog;
-import org.wordpress.android.util.AppLog.T;
-
-class FollowListener implements FollowRow.OnFollowListener {
- private final int mNoteId;
-
- public FollowListener(int noteId) {
- super();
- mNoteId = noteId;
- }
-
- class FollowResponseHandler implements Listener, ErrorListener {
- private final FollowRow mRow;
- private final String mSiteId;
- private final boolean mShouldFollow;
-
- FollowResponseHandler(FollowRow row, String siteId, boolean shouldFollow) {
- mRow = row;
- mSiteId = siteId;
- mShouldFollow = shouldFollow;
- disableFollowButton();
- }
-
- @Override
- public void onResponse(JSONObject response) {
- if (mRow.isSiteId(mSiteId)) {
- mRow.setFollowing(mShouldFollow);
- }
- enableFollowButton();
-
- // update the associated note so it has the correct follow status
- //NotificationUtils.updateNotification(mNoteId, null);
- }
-
- @Override
- public void onErrorResponse(VolleyError error) {
- enableFollowButton();
- AppLog.d(T.NOTIFS, String.format("Failed to follow the blog: %s ", error));
- }
-
- public void disableFollowButton() {
- if (mRow.isSiteId(mSiteId)) {
- mRow.getFollowButton().setEnabled(false);
- }
- }
-
- public void enableFollowButton() {
- if (mRow.isSiteId(mSiteId)) {
- mRow.getFollowButton().setEnabled(true);
- }
- }
- }
-
- @Override
- public void onFollow(final FollowRow row, final String siteId) {
- FollowResponseHandler handler = new FollowResponseHandler(row, siteId, true);
- WordPress.getRestClientUtils().followSite(siteId, handler, handler);
- }
-
- @Override
- public void onUnfollow(final FollowRow row, final String siteId) {
- FollowResponseHandler handler = new FollowResponseHandler(row, siteId, false);
- WordPress.getRestClientUtils().unfollowSite(siteId, handler, handler);
- }
-}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/FollowRow.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/FollowRow.java
deleted file mode 100644
index ac7924deaaac..000000000000
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/FollowRow.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/**
- * A row with and avatar, name and follow button
- *
- * The follow button switches between "Follow" and "Unfollow" depending on the follow status
- * and provides and interface to know when the user has tried to follow or unfollow by tapping
- * the button.
- *
- * Potentially can integrate with Gravatar using the avatar url to find profile JSON.
- */
-package org.wordpress.android.ui.notifications;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.volley.toolbox.NetworkImageView;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.wordpress.android.R;
-import org.wordpress.android.ui.reader.ReaderAnim;
-import org.wordpress.android.util.AppLog;
-import org.wordpress.android.util.AppLog.T;
-import org.wordpress.android.util.NetworkUtils;
-import org.wordpress.android.util.HtmlUtils;
-
-public class FollowRow extends LinearLayout {
- public static interface OnFollowListener {
- public void onUnfollow(FollowRow row, String blogId);
- public void onFollow(FollowRow row, String blogId);
- }
-
- private static final String PARAMS_FIELD = "params";
- private static final String TYPE_FIELD = "type";
- private static final String ACTION_TYPE = "follow";
- private static final String BLOG_ID_PARAM = "blog_id";
- private static final String IS_FOLLOWING_PARAM = "is_following";
- private static final String BLOG_URL_PARAM = "blog_url";
- private static final String BLOG_DOMAIN_PARAM = "blog_domain";
-
- private OnFollowListener mFollowListener;
- private JSONObject mParams;
- private String mBlogURL;
-
- public FollowRow(Context context) {
- super(context);
- }
-
- public FollowRow(Context context, AttributeSet attributes) {
- super(context, attributes);
- }
-
- public FollowRow(Context context, AttributeSet attributes, int defStyle) {
- super(context, attributes, defStyle);
- }
-
- void setAction(JSONObject actionJSON) {
- final TextView followButton = getFollowButton();
-
- getImageView().setDefaultImageResId(R.drawable.placeholder);
- try {
- if (actionJSON.has(TYPE_FIELD) && actionJSON.getString(TYPE_FIELD).equals(ACTION_TYPE)) {
- // get the params for following
- mParams = actionJSON.getJSONObject(PARAMS_FIELD);
- // show the button
- followButton.setVisibility(VISIBLE);
- followButton.setOnClickListener(new ClickListener());
- followButton.setOnLongClickListener(new LongClickListener());
- setClickable(true);
- } else {
- mParams = null;
- followButton.setVisibility(GONE);
- followButton.setOnClickListener(null);
- setClickable(false);
- }
-
- if (hasParams()) {
- setSiteUrl(mParams.optString(BLOG_URL_PARAM, null));
- } else {
- setSiteUrl(null);
- }
-
- updateFollowButton(isFollowing());
-
- } catch (JSONException e) {
- AppLog.e(T.NOTIFS, String.format("Could not set action from %s", actionJSON), e);
- followButton.setVisibility(GONE);
- setSiteUrl(null);
- setClickable(false);
- mParams = null;
- }
- }
-
- private boolean hasParams() {
- return mParams != null;
- }
-
- NetworkImageView getImageView() {
- return (NetworkImageView) findViewById(R.id.avatar);
- }
-
- TextView getFollowButton() {
- return (TextView) findViewById(R.id.text_follow);
- }
-
- TextView getNameTextView() {
- return (TextView) findViewById(R.id.name);
- }
-
- TextView getSiteTextView() {
- return (TextView) findViewById(R.id.url);
- }
-
- void setNameText(String text) {
- TextView nameText = getNameTextView();
- if (TextUtils.isEmpty(text)) {
- nameText.setVisibility(View.GONE);
- } else {
- // text may contain html entities, so it must be unescaped for display
- nameText.setText(HtmlUtils.fastUnescapeHtml(text));
- nameText.setVisibility(View.VISIBLE);
- }
- }
-
- boolean isSiteId(String siteId) {
- String thisSiteId = getSiteId();
- return (thisSiteId != null && thisSiteId.equals(siteId));
- }
-
- void setFollowing(boolean following) {
- if (hasParams()) {
- try {
- mParams.putOpt(IS_FOLLOWING_PARAM, following);
- } catch (JSONException e) {
- AppLog.e(T.NOTIFS, String.format("Could not set following %b", following), e);
- }
- }
- updateFollowButton(following);
- }
-
- boolean isFollowing() {
- return hasParams() && mParams.optBoolean(IS_FOLLOWING_PARAM, false);
- }
-
- String getSiteId() {
- if (hasParams()) {
- return mParams.optString(BLOG_ID_PARAM, null);
- } else {
- return null;
- }
- }
-
- void setSiteUrl(String url) {
- mBlogURL = url;
- final TextView siteTextView = getSiteTextView();
-
- if (!TextUtils.isEmpty(url)) {
- siteTextView.setText(getSiteDomain());
- siteTextView.setVisibility(View.VISIBLE);
- this.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mBlogURL != null) {
- NotificationsWebViewActivity.openUrl(getContext(), mBlogURL);
- }
- }
- });
- } else {
- this.setOnClickListener(null);
- siteTextView.setVisibility(View.GONE);
- }
- }
-
- private String getSiteDomain() {
- if (hasParams()) {
- return mParams.optString(BLOG_DOMAIN_PARAM, null);
- } else {
- return null;
- }
- }
-
- private OnFollowListener getFollowListener() {
- return mFollowListener;
- }
-
- void setFollowListener(OnFollowListener listener) {
- mFollowListener = listener;
- }
-
- private boolean hasFollowListener() {
- return mFollowListener != null;
- }
-
- private void updateFollowButton(boolean isFollowing) {
- final TextView followButton = getFollowButton();
- int drawableId = (isFollowing ? R.drawable.note_icon_following : R.drawable.note_icon_follow);
- followButton.setCompoundDrawablesWithIntrinsicBounds(drawableId, 0, 0, 0);
- followButton.setSelected(isFollowing);
- followButton.setText(isFollowing ? R.string.reader_btn_unfollow : R.string.reader_btn_follow);
- }
-
- private class ClickListener implements View.OnClickListener {
- public void onClick(View v) {
- if (!hasFollowListener()) {
- return;
- }
-
- // first make sure we have a connection
- if (!NetworkUtils.checkConnection(getContext()))
- return;
-
- // show new follow state and animate button right away (before network call)
- updateFollowButton(!isFollowing());
- ReaderAnim.animateFollowButton(getFollowButton());
-
- if (isFollowing()) {
- getFollowListener().onUnfollow(FollowRow.this, getSiteId());
- } else {
- getFollowListener().onFollow(FollowRow.this, getSiteId());
- }
- }
- }
-
- private class LongClickListener implements View.OnLongClickListener {
- @Override
- public boolean onLongClick(View v) {
- Toast.makeText(getContext(), getResources().getString(R.string.tooltip_follow), Toast.LENGTH_SHORT).show();
- return true;
- }
- }
-}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteCommentLikeFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteCommentLikeFragment.java
deleted file mode 100644
index 7499ec5ccfc1..000000000000
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteCommentLikeFragment.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/**
- * Behaves much list a ListFragment
- */
-package org.wordpress.android.ui.notifications;
-
-import android.app.ListFragment;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.wordpress.android.R;
-import org.wordpress.android.models.Note;
-import org.wordpress.android.util.JSONUtil;
-
-public class NoteCommentLikeFragment extends ListFragment implements NotificationFragment {
- private Note mNote;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- return inflater.inflate(R.layout.notifications_follow_list, container, false);
- }
-
- @Override
- public void onActivityCreated(Bundle bundle){
- super.onActivityCreated(bundle);
-
- ListView list = getListView();
- list.setDivider(getResources().getDrawable(R.drawable.list_divider));
- list.setDividerHeight(1);
- list.setHeaderDividersEnabled(false);
-
- // No note? No service.
- if (getNote() == null)
- return;
-
- JSONArray bodyItems = getNote().queryJSON("body.items", new JSONArray());
- JSONObject bodyObject = getNote().queryJSON("body", new JSONObject());
-
- // header subject will be the note subject ("These people like your comment"), header
- // snippet will be a snippet of the comment
- final String headerSubject = getHeaderText(bodyItems);
- final String headerSnippet = getCommentSnippet(bodyItems);
- final String headerLink = (bodyObject != null ? JSONUtil.getString(bodyObject, "header_link") : "");
-
- final DetailHeader noteHeader = (DetailHeader) getView().findViewById(R.id.header);
-
- // full header text is the subject + quoted snippet
- if (TextUtils.isEmpty(headerSnippet)) {
- noteHeader.setText(headerSubject);
- } else {
- noteHeader.setText(headerSubject + " \"" + headerSnippet + "\"");
- }
-
- noteHeader.setNote(getNote(), headerLink);
-
- if (getActivity() instanceof OnPostClickListener) {
- noteHeader.setOnPostClickListener(((OnPostClickListener)getActivity()));
- }
- if (getActivity() instanceof OnCommentClickListener) {
- noteHeader.setOnCommentClickListener(((OnCommentClickListener)getActivity()));
- }
-
- setListAdapter(new NoteFollowAdapter(getActivity(), getNote(), true));
- }
-
- @Override
- public void setNote(Note note){
- mNote = note;
- }
-
- @Override
- public Note getNote(){
- return mNote;
- }
-
- private String getHeaderText(JSONArray bodyItems) {
- if (bodyItems == null)
- return "";
- JSONObject noteItem = JSONUtil.queryJSON(bodyItems, String.format("[%d]", 0), new JSONObject());
- return JSONUtil.getStringDecoded(noteItem, "header_text");
- }
-
- private String getCommentSnippet(JSONArray bodyItems) {
- if (bodyItems == null)
- return "";
- JSONObject noteItem = JSONUtil.queryJSON(bodyItems, String.format("[%d]", 0), new JSONObject());
- return JSONUtil.getStringDecoded(noteItem, "html");
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- if (outState.isEmpty()) {
- outState.putBoolean("bug_19917_fix", true);
- }
- super.onSaveInstanceState(outState);
- }
-}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteFollowAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteFollowAdapter.java
deleted file mode 100644
index e3c785201130..000000000000
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteFollowAdapter.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.wordpress.android.ui.notifications;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.wordpress.android.R;
-import org.wordpress.android.WordPress;
-import org.wordpress.android.models.Note;
-import org.wordpress.android.util.AppLog;
-import org.wordpress.android.util.HtmlUtils;
-import org.wordpress.android.util.JSONUtil;
-import org.wordpress.android.util.PhotonUtils;
-import org.wordpress.android.util.StringUtils;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Adapter used by NoteSingleLineListFragment and
- * NoteCommentLikeFragment to display list of liking/following users which enables
- * following/unfollowing each of them
- */
-public class NoteFollowAdapter extends BaseAdapter {
- private JSONArray mItems;
- private Note mNote;
- private final boolean mDiscardFirstItem;
- private final int mAvatarSz;
- private final WeakReference mWeakContext;
- private final LayoutInflater mInflater;
-
- NoteFollowAdapter(Context context, Note note, boolean discardFirstItem) {
- mWeakContext = new WeakReference(context);
- mInflater = LayoutInflater.from(context);
- mDiscardFirstItem = discardFirstItem;
- mAvatarSz = context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_medium);
-
- setNote(note);
-
- // request the latest version of this note to ensure follow statuses are correct
- //NotificationUtils.updateNotification(getNoteId(), this);
- }
-
- /*
- * fired by NotificationUtils.updateNotification() when this note has been updated
- */
- /*@Override
- public void onNoteUpdated(int noteId) {
- if (hasContext())
- setNote(WordPress.wpDB.getNoteById(noteId));
- }*/
-
- private boolean hasContext() {
- return (mWeakContext.get() != null);
- }
-
- private void setNote(Note note) {
- boolean hasItems = (mItems != null);
-
- mNote = note;
-
- final JSONArray items;
- if (mNote != null) {
- items = mNote.queryJSON("body.items", new JSONArray());
- } else {
- items = new JSONArray();
- }
-
- // the first body item in comment likes is the header ("This person liked your comment")
- // and should be discarded
- if (mDiscardFirstItem && items.length() > 0) {
- // can't use mItems.remove(0) since it requires API 19
- mItems = new JSONArray();
- for (int i = 1; i < items.length(); i++) {
- try {
- mItems.put(items.get(i));
- } catch (JSONException e) {
- AppLog.e(AppLog.T.NOTIFS, e);
- }
- }
- } else {
- mItems = items;
- }
-
- // if the adapter had existing items, make sure the changes are reflected
- if (hasItems) {
- notifyDataSetChanged();
- }
- }
-
- public View getView(int position, View cachedView, ViewGroup parent){
- View view;
- if (cachedView == null) {
- view = mInflater.inflate(R.layout.notifications_follow_row, null);
- } else {
- view = cachedView;
- }
-
- JSONObject noteItem = getItem(position);
- JSONObject followAction = JSONUtil.queryJSON(noteItem, "action", new JSONObject());
-
- FollowRow row = (FollowRow) view;
- row.setFollowListener(new FollowListener(getNoteId()));
- row.setAction(followAction);
-
- String headerText = JSONUtil.queryJSON(noteItem, "header_text", "");
- if (TextUtils.isEmpty(headerText)) {
- // reblog notifications don't have "header_text" but they do have "header" which
- // contains the user's name wrapped in a link, so strip the html to get the name
- headerText = HtmlUtils.fastStripHtml(JSONUtil.queryJSON(noteItem, "header", ""));
- }
- row.setNameText(headerText);
-
- String iconUrl = JSONUtil.queryJSON(noteItem, "icon", "");
- row.getImageView().setImageUrl(PhotonUtils.fixAvatar(iconUrl, mAvatarSz), WordPress.imageLoader);
-
- return view;
- }
-
- public long getItemId(int position){
- return position;
- }
-
- public JSONObject getItem(int position){
- return JSONUtil.queryJSON(mItems, String.format("[%d]", position), new JSONObject());
- }
-
- public int getCount(){
- return (mItems != null ? mItems.length() : 0);
- }
-
- private int getNoteId() {
- if (mNote == null)
- return 0;
- return StringUtils.stringToInt(mNote.getId());
- }
-}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteMatcherFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteMatcherFragment.java
deleted file mode 100644
index 773fb310952f..000000000000
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteMatcherFragment.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.wordpress.android.ui.notifications;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.text.Html;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.volley.toolbox.NetworkImageView;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.wordpress.android.R;
-import org.wordpress.android.WordPress;
-import org.wordpress.android.models.Note;
-import org.wordpress.android.util.JSONUtil;
-import org.wordpress.android.util.WPLinkMovementMethod;
-
-public class NoteMatcherFragment extends Fragment implements NotificationFragment {
- private Note mNote;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle state){
- View view = inflater.inflate(R.layout.notifications_matcher, parent, false);
-
- // No note? No service.
- if (getNote() == null)
- return view;
-
- JSONObject noteBody = getNote().queryJSON("body", new JSONObject());
- JSONArray noteBodyItems = getNote().queryJSON("body.items", new JSONArray());
- JSONObject noteBodyItemAtPositionZero = JSONUtil.queryJSON(noteBodyItems, String.format("[%d]", 0), new JSONObject());
-
- DetailHeader noteHeader = (DetailHeader) view.findViewById(R.id.header);
- JSONObject subject = getNote().queryJSON("subject", new JSONObject());
- String headerText = JSONUtil.getStringDecoded(subject, "text");
- noteHeader.setText(headerText);
- noteHeader.setClickable(false);
-
- String gravURL = JSONUtil.queryJSON(noteBodyItemAtPositionZero, "icon", "");
- if (!gravURL.equals("")) {
- NetworkImageView mBadgeImageView = (NetworkImageView) view.findViewById(R.id.gravatar);
- mBadgeImageView.setImageUrl(gravURL, WordPress.imageLoader);
- }
-
- TextView bodyTextView = (TextView) view.findViewById(R.id.body);
- bodyTextView.setMovementMethod(WPLinkMovementMethod.getInstance());
- String noteHTML = JSONUtil.getString(noteBodyItemAtPositionZero, "html");
- bodyTextView.setText(Html.fromHtml(noteHTML));
-
- //setup the footer
- DetailHeader noteFooter = (DetailHeader) view.findViewById(R.id.footer);
- String footerText = JSONUtil.getStringDecoded(noteBody, "header_text");
- noteFooter.setText(footerText);
- JSONObject bodyObject = getNote().queryJSON("body", new JSONObject());
- String itemURL = JSONUtil.getString(bodyObject, "header_link");
- noteFooter.setNote(getNote(), itemURL);
-
- if (getActivity() instanceof OnPostClickListener) {
- noteFooter.setOnPostClickListener(((OnPostClickListener)getActivity()));
- }
- if (getActivity() instanceof OnCommentClickListener) {
- noteFooter.setOnCommentClickListener(((OnCommentClickListener)getActivity()));
- }
-
- return view;
- }
-
- public void setNote(Note note){
- mNote = note;
- }
- public Note getNote(){
- return mNote;
- }
-}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteSingleLineListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteSingleLineListFragment.java
deleted file mode 100644
index 6a6fa96a0bd5..000000000000
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NoteSingleLineListFragment.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
- * Behaves much list a ListFragment
- */
-package org.wordpress.android.ui.notifications;
-
-import android.app.ListFragment;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-import org.json.JSONObject;
-import org.wordpress.android.R;
-import org.wordpress.android.models.Note;
-import org.wordpress.android.util.JSONUtil;
-
-public class NoteSingleLineListFragment extends ListFragment implements NotificationFragment {
- private Note mNote;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- return inflater.inflate(R.layout.notifications_follow_list, container, false);
- }
-
- @Override
- public void onActivityCreated(Bundle bundle){
- super.onActivityCreated(bundle);
-
- ListView list = getListView();
- list.setDivider(getResources().getDrawable(R.drawable.list_divider));
- list.setDividerHeight(1);
- list.setHeaderDividersEnabled(false);
-
- // No note? No service.
- if (getNote() == null)
- return;
-
- // set the header
- final DetailHeader noteHeader = (DetailHeader) getView().findViewById(R.id.header);
- noteHeader.setText(JSONUtil.getStringDecoded(getNote().queryJSON("subject", new JSONObject()), "text"));
- String footerUrl = getNote().queryJSON("body.header_link", "");
- noteHeader.setNote(getNote(), footerUrl);
-
- if (getActivity() instanceof OnPostClickListener) {
- noteHeader.setOnPostClickListener(((OnPostClickListener)getActivity()));
- }
- if (getActivity() instanceof OnCommentClickListener) {
- noteHeader.setOnCommentClickListener(((OnCommentClickListener)getActivity()));
- }
-
- setListAdapter(new NoteFollowAdapter(getActivity(), getNote(), false));
- }
-
- @Override
- public void setNote(Note note){
- mNote = note;
- }
-
- @Override
- public Note getNote(){
- return mNote;
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- if (outState.isEmpty()) {
- outState.putBoolean("bug_19917_fix", true);
- }
- super.onSaveInstanceState(outState);
- }
-}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotesAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotesAdapter.java
index e7d05f7750b6..fd9a62cc96e7 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotesAdapter.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotesAdapter.java
@@ -2,41 +2,58 @@
import android.content.Context;
import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.support.v4.widget.CursorAdapter;
-import android.util.Log;
+import android.text.Html;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
+import android.widget.CursorAdapter;
import android.widget.TextView;
import com.simperium.client.Bucket;
+import com.simperium.client.BucketObjectMissingException;
import com.simperium.client.Query;
import org.wordpress.android.R;
import org.wordpress.android.models.Note;
-import org.wordpress.android.util.AppLog;
-import org.wordpress.android.util.DisplayUtils;
import org.wordpress.android.util.PhotonUtils;
+import org.wordpress.android.util.SqlUtils;
+import org.wordpress.android.util.StringUtils;
+import org.wordpress.android.widgets.NoticonTextView;
import org.wordpress.android.widgets.WPNetworkImageView;
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
class NotesAdapter extends CursorAdapter {
private final int mAvatarSz;
private final Query mQuery;
- private final Context mContext;
+ private final Bucket mNotesBucket;
+ private final int mReadBackgroundResId;
+ private final int mUnreadBackgroundResId;
+ private final List mHiddenNoteIds = new ArrayList();
+ private final List mModeratingNoteIds = new ArrayList();
NotesAdapter(Context context, Bucket bucket) {
super(context, null, 0x0);
- mContext = context;
+ mNotesBucket = bucket;
// build a query that sorts by timestamp descending
- mQuery = bucket.query().order(Note.Schema.TIMESTAMP_INDEX, Query.SortType.DESCENDING);
-
- mAvatarSz = DisplayUtils.dpToPx(context, 48);
+ mQuery = bucket.query()
+ .include(
+ Note.Schema.TIMESTAMP_INDEX,
+ Note.Schema.SUBJECT_INDEX,
+ Note.Schema.SNIPPET_INDEX,
+ Note.Schema.UNREAD_INDEX,
+ Note.Schema.ICON_URL_INDEX,
+ Note.Schema.NOTICON_INDEX)
+ .order(Note.Schema.TIMESTAMP_INDEX, Query.SortType.DESCENDING);
+
+
+ mAvatarSz = (int) context.getResources().getDimension(R.dimen.avatar_sz_large);
+ mReadBackgroundResId = R.drawable.list_bg_selector;
+ mUnreadBackgroundResId = R.drawable.list_unread_bg_selector;
}
public void closeCursor() {
@@ -46,22 +63,52 @@ public void closeCursor() {
}
}
+ public Note getNote(int position) {
+ if (getCursor() == null) {
+ return null;
+ }
+
+ Bucket.ObjectCursor cursor = (Bucket.ObjectCursor)getCursor();
+
+ if (cursor.moveToPosition(position)) {
+ String noteId = cursor.getSimperiumKey();
+ try {
+ return mNotesBucket.get(noteId);
+ } catch (BucketObjectMissingException e) {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
public void reloadNotes() {
changeCursor(mQuery.execute());
}
- public Note getNote(int position) {
- getCursor().moveToPosition(position);
- return getNote();
+ public void addHiddenNoteId(String noteId) {
+ mHiddenNoteIds.add(noteId);
+ notifyDataSetChanged();
}
- private Note getNote() {
- return ((Bucket.ObjectCursor) getCursor()).getObject();
+ public void removeHiddenNoteId(String noteId) {
+ mHiddenNoteIds.remove(noteId);
+ notifyDataSetChanged();
+ }
+
+ public void addModeratingNoteId(String noteId) {
+ mModeratingNoteIds.add(noteId);
+ notifyDataSetChanged();
+ }
+
+ public void removeModeratingNoteId(String noteId) {
+ mModeratingNoteIds.remove(noteId);
+ notifyDataSetChanged();
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- View view = LayoutInflater.from(context).inflate(R.layout.note_list_item, parent, false);
+ View view = LayoutInflater.from(context).inflate(R.layout.notifications_list_item, parent, false);
NoteViewHolder holder = new NoteViewHolder(view);
view.setTag(holder);
@@ -73,73 +120,139 @@ public void bindView(View view, Context context, Cursor cursor) {
if (cursor.isClosed())
return;
- Bucket.ObjectCursor bucketCursor = (Bucket.ObjectCursor) cursor;
- Note note = bucketCursor.getObject();
+ Bucket.ObjectCursor objectCursor = (Bucket.ObjectCursor) cursor;
+
+ final NoteViewHolder noteViewHolder = (NoteViewHolder) view.getTag();
- NoteViewHolder noteViewHolder = (NoteViewHolder) view.getTag();
+ // Display group header
+ Note.NoteTimeGroup timeGroup = Note.getTimeGroupForTimestamp(getLongForColumnName(objectCursor, Note.Schema.TIMESTAMP_INDEX));
+
+ Note.NoteTimeGroup previousTimeGroup = null;
+ if (objectCursor.getPosition() > 0 && objectCursor.moveToPrevious()) {
+ previousTimeGroup = Note.getTimeGroupForTimestamp(getLongForColumnName(objectCursor, Note.Schema.TIMESTAMP_INDEX));
+ objectCursor.moveToNext();
+ }
+
+ if (previousTimeGroup != null && previousTimeGroup == timeGroup) {
+ noteViewHolder.headerView.setVisibility(View.GONE);
+ } else {
+ if (timeGroup == Note.NoteTimeGroup.GROUP_TODAY) {
+ noteViewHolder.headerText.setText(context.getString(R.string.stats_timeframe_today).toUpperCase());
+ } else if (timeGroup == Note.NoteTimeGroup.GROUP_YESTERDAY) {
+ noteViewHolder.headerText.setText(context.getString(R.string.stats_timeframe_yesterday).toUpperCase());
+ } else if (timeGroup == Note.NoteTimeGroup.GROUP_OLDER_TWO_DAYS) {
+ noteViewHolder.headerText.setText(context.getString(R.string.older_two_days).toUpperCase());
+ } else if (timeGroup == Note.NoteTimeGroup.GROUP_OLDER_WEEK) {
+ noteViewHolder.headerText.setText(context.getString(R.string.older_last_week).toUpperCase());
+ } else {
+ noteViewHolder.headerText.setText(context.getString(R.string.older_month).toUpperCase());
+ }
+
+ noteViewHolder.headerView.setVisibility(View.VISIBLE);
+ }
- noteViewHolder.txtLabel.setText(note.getSubject());
- if (note.isCommentType()) {
- noteViewHolder.txtDetail.setText(note.getCommentPreview());
+ if (mHiddenNoteIds.size() > 0 && mHiddenNoteIds.contains(objectCursor.getSimperiumKey())) {
+ noteViewHolder.contentView.setVisibility(View.GONE);
+ } else {
+ noteViewHolder.contentView.setVisibility(View.VISIBLE);
+ }
+
+ if (mModeratingNoteIds.size() > 0 && mModeratingNoteIds.contains(objectCursor.getSimperiumKey())) {
+ noteViewHolder.progressBar.setVisibility(View.VISIBLE);
+ } else {
+ noteViewHolder.progressBar.setVisibility(View.GONE);
+ }
+
+ // Subject is stored in db as html to preserve text formatting
+ String noteSubjectHtml = getStringForColumnName(objectCursor, Note.Schema.SUBJECT_INDEX).trim();
+ CharSequence noteSubjectSpanned = Html.fromHtml(noteSubjectHtml);
+ // Trim the '\n\n' added by Html.fromHtml()
+ noteSubjectSpanned = noteSubjectSpanned.subSequence(0, TextUtils.getTrimmedLength(noteSubjectSpanned));
+ noteViewHolder.txtLabel.setText(noteSubjectSpanned);
+
+ String noteSnippet = getStringForColumnName(objectCursor, Note.Schema.SNIPPET_INDEX);
+ if (!TextUtils.isEmpty(noteSnippet)) {
+ noteViewHolder.txtLabel.setMaxLines(2);
+ noteViewHolder.txtDetail.setText(noteSnippet);
noteViewHolder.txtDetail.setVisibility(View.VISIBLE);
} else {
+ noteViewHolder.txtLabel.setMaxLines(3);
noteViewHolder.txtDetail.setVisibility(View.GONE);
}
- noteViewHolder.txtDate.setText(note.getTimeSpan());
-
- String avatarUrl = PhotonUtils.fixAvatar(note.getIconURL(), mAvatarSz);
+ String avatarUrl = PhotonUtils.fixAvatar(getStringForColumnName(objectCursor, Note.Schema.ICON_URL_INDEX), mAvatarSz);
noteViewHolder.imgAvatar.setImageUrl(avatarUrl, WPNetworkImageView.ImageType.AVATAR);
- noteViewHolder.imgNoteIcon.setImageDrawable(getDrawableForType(note.getType()));
+ boolean isUnread = SqlUtils.sqlToBool(getIntForColumnName(objectCursor, Note.Schema.UNREAD_INDEX));
+
+ String noticonCharacter = getStringForColumnName(objectCursor, Note.Schema.NOTICON_INDEX);
+ if (!TextUtils.isEmpty(noticonCharacter)) {
+ noteViewHolder.noteIcon.setText(noticonCharacter);
+ if (isUnread) {
+ noteViewHolder.noteIcon.setBackgroundResource(R.drawable.shape_oval_blue);
+ } else {
+ noteViewHolder.noteIcon.setBackgroundResource(R.drawable.shape_oval_grey);
+ }
+ noteViewHolder.noteIcon.setVisibility(View.VISIBLE);
+ } else {
+ noteViewHolder.noteIcon.setVisibility(View.GONE);
+ }
- noteViewHolder.unreadIndicator.setVisibility(note.isUnread() ? View.VISIBLE : View.INVISIBLE);
+ if (isUnread) {
+ view.setBackgroundResource(mUnreadBackgroundResId);
+ } else {
+ view.setBackgroundResource(mReadBackgroundResId);
+ }
}
- // HashMap of drawables for note types
- private final HashMap mNoteIcons = new HashMap();
+ private String getStringForColumnName(Cursor cursor, String columnName) {
+ if (columnName == null || cursor == null || cursor.getColumnIndex(columnName) == -1) {
+ return "";
+ }
- private Drawable getDrawableForType(String noteType) {
- if (mContext == null || noteType == null)
- return null;
+ return StringUtils.notNullStr(cursor.getString(cursor.getColumnIndex(columnName)));
+ }
- // use like icon for comment likes
- if (noteType.equals(Note.NOTE_COMMENT_LIKE_TYPE))
- noteType = Note.NOTE_LIKE_TYPE;
+ private int getIntForColumnName(Cursor cursor, String columnName) {
+ if (columnName == null || cursor == null || cursor.getColumnIndex(columnName) == -1) {
+ return -1;
+ }
- Drawable icon = mNoteIcons.get(noteType);
- if (icon != null)
- return icon;
+ return cursor.getInt(cursor.getColumnIndex(columnName));
+ }
- int imageId = mContext.getResources().getIdentifier("note_icon_" + noteType, "drawable", mContext.getPackageName());
- if (imageId == 0) {
- Log.w(AppLog.TAG, "unknown note type - " + noteType);
- return null;
+ private long getLongForColumnName(Cursor cursor, String columnName) {
+ if (columnName == null || cursor == null || cursor.getColumnIndex(columnName) == -1) {
+ return -1;
}
- icon = mContext.getResources().getDrawable(imageId);
- if (icon == null)
- return null;
+ return cursor.getLong(cursor.getColumnIndex(columnName));
+ }
- mNoteIcons.put(noteType, icon);
- return icon;
+ public boolean isModeratingNote(String noteId) {
+ return mModeratingNoteIds.contains(noteId);
}
- private static class NoteViewHolder {
+ public static class NoteViewHolder {
+ private final View headerView;
+ private final View contentView;
+ private final TextView headerText;
+
private final TextView txtLabel;
private final TextView txtDetail;
- private final TextView unreadIndicator;
- private final TextView txtDate;
private final WPNetworkImageView imgAvatar;
- private final ImageView imgNoteIcon;
+ private final NoticonTextView noteIcon;
+ private final View progressBar;
NoteViewHolder(View view) {
- txtLabel = (TextView) view.findViewById(R.id.note_label);
+ headerView = view.findViewById(R.id.time_header);
+ contentView = view.findViewById(R.id.note_content_container);
+ headerText = (TextView)view.findViewById(R.id.header_date_text);
+ txtLabel = (TextView) view.findViewById(R.id.note_subject);
txtDetail = (TextView) view.findViewById(R.id.note_detail);
- unreadIndicator = (TextView) view.findViewById(R.id.unread_indicator);
- txtDate = (TextView) view.findViewById(R.id.text_date);
imgAvatar = (WPNetworkImageView) view.findViewById(R.id.note_avatar);
- imgNoteIcon = (ImageView) view.findViewById(R.id.note_icon);
+ noteIcon = (NoticonTextView) view.findViewById(R.id.note_icon);
+ progressBar = view.findViewById(R.id.moderate_progress);
}
}
}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationFragment.java
index a569d6223dbd..6690fd0779cc 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationFragment.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationFragment.java
@@ -14,10 +14,6 @@ public static interface OnPostClickListener {
public void onPostClicked(Note note, int remoteBlogId, int postId);
}
- public static interface OnCommentClickListener {
- public void onCommentClicked(Note note, int remoteBlogId, long commentId);
- }
-
public Note getNote();
public void setNote(Note note);
-}
\ No newline at end of file
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsActivity.java
index bc104caf6c70..21cb77c0269a 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsActivity.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsActivity.java
@@ -1,84 +1,108 @@
package org.wordpress.android.ui.notifications;
import android.app.ActionBar;
-import android.app.Dialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.View;
+import android.widget.ListView;
+import com.cocosw.undobar.UndoBarController;
import com.simperium.client.Bucket;
import com.simperium.client.BucketObjectMissingException;
-import com.simperium.client.User;
import org.wordpress.android.GCMIntentService;
import org.wordpress.android.R;
import org.wordpress.android.analytics.AnalyticsTracker;
-import org.wordpress.android.models.BlogPairId;
+import org.wordpress.android.models.CommentStatus;
import org.wordpress.android.models.Note;
import org.wordpress.android.ui.WPActionBarActivity;
import org.wordpress.android.ui.comments.CommentActions;
+import org.wordpress.android.ui.comments.CommentDetailActivity;
import org.wordpress.android.ui.comments.CommentDetailFragment;
-import org.wordpress.android.ui.comments.CommentDialogs;
+import org.wordpress.android.ui.notifications.utils.SimperiumUtils;
+import org.wordpress.android.ui.reader.ReaderActivityLauncher;
import org.wordpress.android.ui.reader.ReaderPostDetailFragment;
import org.wordpress.android.ui.reader.actions.ReaderAuthActions;
+import org.wordpress.android.ui.stats.StatsActivity;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.android.util.AuthenticationDialogUtils;
-import org.wordpress.android.util.StringUtils;
+import org.wordpress.android.util.ToastUtils;
-public class NotificationsActivity extends WPActionBarActivity
- implements CommentActions.OnCommentChangeListener, NotificationFragment.OnPostClickListener,
- NotificationFragment.OnCommentClickListener {
+import javax.annotation.Nonnull;
+
+public class NotificationsActivity extends WPActionBarActivity implements CommentActions.OnNoteCommentActionListener,
+ CommentActions.OnCommentChangeListener {
public static final String NOTIFICATION_ACTION = "org.wordpress.android.NOTIFICATION";
public static final String NOTE_ID_EXTRA = "noteId";
public static final String FROM_NOTIFICATION_EXTRA = "fromNotification";
public static final String NOTE_INSTANT_REPLY_EXTRA = "instantReply";
- private static final String KEY_INITIAL_UPDATE = "initial_update";
- private static final String KEY_SELECTED_COMMENT_ID = "selected_comment_id";
- private static final String KEY_SELECTED_POST_ID = "selected_post_id";
- private NotificationsListFragment mNotesList;
- private boolean mDualPane;
- private int mSelectedNoteId;
+ private static final String KEY_INITIAL_UPDATE = "initialUpdate";
+ private static final String KEY_REPLY_TEXT = "replyText";
+ private static final String KEY_LIST_POSITION = "listPosition";
+
+ private static final String TAG_LIST_VIEW = "notificationsList";
+ private static final String TAG_TABLET_DETAIL_VIEW = "notificationsTabletDetail";
+
+ private NotificationsListFragment mNotesListFragment;
+ private Fragment mDetailFragment;
+
+ private String mSelectedNoteId;
+ private String mRestoredReplyText;
private boolean mHasPerformedInitialUpdate;
- private BlogPairId mTmpSelectedComment;
- private BlogPairId mTmpSelectedReaderPost;
- private BlogPairId mSelectedComment;
- private BlogPairId mSelectedReaderPost;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
- createMenuDrawer(R.layout.notifications);
- // savedInstanceState will be non-null if activity is being re-created
+ createMenuDrawer(R.layout.notifications_activity);
+
if (savedInstanceState == null) {
AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATIONS_ACCESSED);
}
- View fragmentContainer = findViewById(R.id.layout_fragment_container);
- mDualPane = fragmentContainer != null && getString(R.string.dual_pane_mode).equals(fragmentContainer.getTag());
+ FragmentManager fragmentManager = getFragmentManager();
+
+ if (mNotesListFragment == null) {
+ mNotesListFragment = (NotificationsListFragment)fragmentManager.findFragmentByTag(TAG_LIST_VIEW);
+ }
ActionBar actionBar = getActionBar();
- actionBar.setDisplayShowTitleEnabled(true);
+ if (actionBar != null) {
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
setTitle(getResources().getString(R.string.notifications));
- FragmentManager fm = getFragmentManager();
- fm.addOnBackStackChangedListener(mOnBackStackChangedListener);
- mNotesList = (NotificationsListFragment) fm.findFragmentById(R.id.fragment_notes_list);
- mNotesList.setOnNoteClickListener(new NoteClickListener());
+ fragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener);
+ mNotesListFragment.setOnNoteClickListener(new NoteClickListener());
GCMIntentService.clearNotificationsMap();
if (savedInstanceState != null) {
mHasPerformedInitialUpdate = savedInstanceState.getBoolean(KEY_INITIAL_UPDATE);
- popNoteDetail();
+
+ if (savedInstanceState.getString(KEY_REPLY_TEXT) != null){
+ mRestoredReplyText = savedInstanceState.getString(KEY_REPLY_TEXT);
+ }
+
+ if (savedInstanceState.getString(NOTE_ID_EXTRA) != null) {
+ // Restore last selected note
+ openNoteForNoteId(savedInstanceState.getString(NOTE_ID_EXTRA));
+ }
+
+ if (savedInstanceState.containsKey(KEY_LIST_POSITION)) {
+ mNotesListFragment.setRestoredListPosition(
+ savedInstanceState.getInt(KEY_LIST_POSITION, ListView.INVALID_POSITION)
+ );
+ }
} else {
launchWithNoteId();
}
@@ -105,7 +129,7 @@ protected void onResume() {
super.onResume();
// Remove notification if it is showing when we resume this activity.
- NotificationManager notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
+ NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.cancel(GCMIntentService.PUSH_NOTIFICATION_ID);
if (SimperiumUtils.isUserAuthorized()) {
@@ -114,42 +138,39 @@ protected void onResume() {
}
@Override
- protected void onDestroy() {
- if (mNotesList != null) {
- mNotesList.closeAdapterCursor();
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ FragmentManager fm = getFragmentManager();
+ if (fm.getBackStackEntryCount() > 0) {
+ mRestoredReplyText = getCommentReplyText();
+ popNoteDetail();
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ default:
+ return super.onOptionsItemSelected(item);
}
+ }
- super.onDestroy();
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.notifications, menu);
+ return true;
}
private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
int backStackEntryCount = getFragmentManager().getBackStackEntryCount();
- // This is ugly, but onBackStackChanged is not called just after a fragment commit.
- // In a 2 commits in a row case, onBackStackChanged is called twice but after the
- // 2 commits. That's why mSelectedPostId can't be affected correctly after the first commit.
- switch (backStackEntryCount) {
- case 2:
- mSelectedReaderPost = mTmpSelectedReaderPost;
- mSelectedComment = mTmpSelectedComment;
- mTmpSelectedReaderPost = null;
- mTmpSelectedComment = null;
- break;
- case 1:
- if (mDualPane) {
- mSelectedReaderPost = mTmpSelectedReaderPost;
- mSelectedComment = mTmpSelectedComment;
- } else {
- mSelectedReaderPost = null;
- mSelectedComment = null;
- }
- break;
- case 0:
- mMenuDrawer.setDrawerIndicatorEnabled(true);
- mSelectedReaderPost = null;
- mSelectedComment = null;
- break;
+ if (backStackEntryCount == 0) {
+ mMenuDrawer.setDrawerIndicatorEnabled(true);
+ setTitle(R.string.notifications);
+ } else {
+ mMenuDrawer.setDrawerIndicatorEnabled(false);
}
}
};
@@ -160,190 +181,257 @@ public void onBackStackChanged() {
private void launchWithNoteId() {
Intent intent = getIntent();
if (intent.hasExtra(NOTE_ID_EXTRA)) {
- String noteID = intent.getStringExtra(NOTE_ID_EXTRA);
-
- Bucket notesBucket = SimperiumUtils.getNotesBucket();
- try {
- if (notesBucket != null) {
- Note note = notesBucket.get(noteID);
- if (note != null) {
- openNote(note);
- }
- }
- } catch (BucketObjectMissingException e) {
- AppLog.e(T.NOTIFS, "Could not load notification from bucket.");
- }
- } else {
- // Dual pane and no note specified then select the first note
- if (mDualPane && mNotesList != null) {
- mNotesList.setShouldLoadFirstNote(true);
- }
+ openNoteForNoteId(intent.getStringExtra(NOTE_ID_EXTRA));
}
}
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- if (mDualPane) {
- // let WPActionBarActivity handle it (toggles menu drawer)
- return super.onOptionsItemSelected(item);
- } else {
- FragmentManager fm = getFragmentManager();
- if (fm.getBackStackEntryCount() > 0) {
- popNoteDetail();
- return true;
- } else {
- return super.onOptionsItemSelected(item);
- }
+ private void openNoteForNoteId(String noteId) {
+ Bucket notesBucket = SimperiumUtils.getNotesBucket();
+ try {
+ if (notesBucket != null) {
+ Note note = notesBucket.get(noteId);
+ if (note != null) {
+ openNote(note);
}
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.notifications, menu);
- return true;
- }
-
- /*
- * triggered from the comment details fragment whenever a comment is changed (moderated, added,
- * deleted, etc.) - refresh notifications so changes are reflected here
- */
- @Override
- public void onCommentChanged(CommentActions.ChangedFrom changedFrom, CommentActions.ChangeType changeType) {
- // remove the comment detail fragment if the comment was trashed
- if (changeType == CommentActions.ChangeType.TRASHED && changedFrom == CommentActions.ChangedFrom.COMMENT_DETAIL) {
- FragmentManager fm = getFragmentManager();
- if (fm.getBackStackEntryCount() > 0) {
- fm.popBackStack();
}
+ } catch (BucketObjectMissingException e) {
+ AppLog.e(T.NOTIFS, "Could not load notification from bucket.");
}
-
- mNotesList.refreshNotes();
}
- void popNoteDetail(){
+ void popNoteDetail() {
FragmentManager fm = getFragmentManager();
- Fragment f = fm.findFragmentById(R.id.fragment_comment_detail);
- if (f == null) {
- fm.popBackStack();
- }
+ fm.popBackStack();
}
/**
* Tries to pick the correct fragment detail type for a given note
+ * Defaults to NotificationDetailListFragment
*/
- private Fragment getDetailFragmentForNote(Note note){
+ private Fragment getDetailFragmentForNote(Note note) {
if (note == null)
return null;
+ Fragment fragment;
if (note.isCommentType()) {
// show comment detail for comment notifications
- return CommentDetailFragment.newInstance(note);
- } else if (note.isCommentLikeType()) {
- return new NoteCommentLikeFragment();
+ CommentDetailFragment commentDetailFragment = CommentDetailFragment.newInstance(note);
+ if (!TextUtils.isEmpty(mRestoredReplyText)) {
+ commentDetailFragment.setRestoredReplyText(mRestoredReplyText);
+ }
+
+ fragment = commentDetailFragment;
} else if (note.isAutomattcherType()) {
// show reader post detail for automattchers about posts - note that comment
// automattchers are handled by note.isCommentType() above
- boolean isPost = (note.getBlogId() !=0 && note.getPostId() != 0 && note.getCommentId() == 0);
+ boolean isPost = (note.getSiteId() != 0 && note.getPostId() != 0 && note.getCommentId() == 0);
if (isPost) {
- return ReaderPostDetailFragment.newInstance(note.getBlogId(), note.getPostId());
+ fragment = ReaderPostDetailFragment.newInstance(note.getSiteId(), note.getPostId());
} else {
- // right now we'll never get here
- return new NoteMatcherFragment();
+ fragment = NotificationsDetailListFragment.newInstance(note);
}
- } else if (note.isSingleLineListTemplate()) {
- return new NoteSingleLineListFragment();
- } else if (note.isBigBadgeTemplate()) {
- return new BigBadgeFragment();
+ } else {
+ fragment = NotificationsDetailListFragment.newInstance(note);
}
- return null;
+ return fragment;
}
/**
- * Open a note fragment based on the type of note
+ * Open a note fragment based on the type of note
*/
private void openNote(final Note note) {
if (note == null || isFinishing() || isActivityDestroyed()) {
return;
}
- mSelectedNoteId = StringUtils.stringToInt(note.getId());
+ mSelectedNoteId = note.getId();
// mark the note as read if it's unread
if (note.isUnread()) {
// mark as read which syncs with simperium
note.markAsRead();
}
+
+ // create detail fragment for this note type
+ mDetailFragment = getDetailFragmentForNote(note);
+
FragmentManager fm = getFragmentManager();
+ FragmentTransaction ft = fm.beginTransaction();
+ ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
+ ft.hide(mNotesListFragment);
+ ft.add(R.id.layout_fragment_container, mDetailFragment);
+ mMenuDrawer.setDrawerIndicatorEnabled(false);
+ ft.addToBackStack(null);
+ ft.commitAllowingStateLoss();
- // remove the note detail if it's already on there
- if (fm.getBackStackEntryCount() > 0) {
- fm.popBackStack();
+ // Update title
+ if (note.getFormattedSubject() != null) {
+ setTitle(note.getTitle());
}
+ }
- // create detail fragment for this note type
- Fragment detailFragment = getDetailFragmentForNote(note);
- if (detailFragment == null) {
- AppLog.d(T.NOTIFS, String.format("No fragment found for %s", note.toJSONObject()));
- return;
+ public void showCommentDetailForNote(Note note) {
+ if (isFinishing() || note == null) return;
+
+ Intent intent = new Intent(this, CommentDetailActivity.class);
+ intent.putExtra(CommentDetailActivity.KEY_COMMENT_DETAIL_IS_REMOTE, true);
+ intent.putExtra(CommentDetailActivity.KEY_COMMENT_DETAIL_NOTE_ID, note.getId());
+ startActivity(intent);
+ }
+
+ public void showBlogPreviewActivity(long siteId, String siteUrl) {
+ if (isFinishing()) return;
+
+ ReaderActivityLauncher.showReaderBlogPreview(this, siteId, siteUrl);
+ }
+
+ public void showPostActivity(long siteId, long postId) {
+ if (isFinishing()) return;
+
+ ReaderActivityLauncher.showReaderPostDetail(this, siteId, postId);
+ }
+
+ public void showStatsActivityForSite(int localTableSiteId) {
+ if (isFinishing()) return;
+
+ Intent intent = new Intent(this, StatsActivity.class);
+ intent.putExtra(StatsActivity.ARG_LOCAL_TABLE_BLOG_ID, localTableSiteId);
+ intent.putExtra(StatsActivity.ARG_NO_MENU_DRAWER, true);
+ startActivity(intent);
+ }
+
+ public void showWebViewActivityForUrl(String url) {
+ if (isFinishing() || url == null) return;
+
+ Intent intent = new Intent(this, NotificationsWebViewActivity.class);
+ intent.putExtra(NotificationsWebViewActivity.URL_TO_LOAD, url);
+ startActivity(intent);
+ }
+
+ @Override
+ public void onModerateCommentForNote(final Note note, final CommentStatus newStatus) {
+ FragmentManager fm = getFragmentManager();
+ if (fm.getBackStackEntryCount() > 0) {
+ fm.popBackStack();
}
- // set the note if this is a NotificationFragment (ReaderPostDetailFragment is the only
- // fragment used here that is not a NotificationFragment)
- if (detailFragment instanceof NotificationFragment) {
- ((NotificationFragment) detailFragment).setNote(note);
+ if (newStatus == CommentStatus.APPROVED || newStatus == CommentStatus.UNAPPROVED) {
+ mNotesListFragment.setNoteIsModerating(note.getId(), true);
+ CommentActions.moderateCommentForNote(note, newStatus,
+ new CommentActions.CommentActionListener() {
+ @Override
+ public void onActionResult(boolean succeeded) {
+ if (isFinishing()) return;
+
+ mNotesListFragment.setNoteIsModerating(note.getId(), false);
+
+ if (!succeeded) {
+ ToastUtils.showToast(NotificationsActivity.this,
+ R.string.error_moderate_comment,
+ ToastUtils.Duration.LONG
+ );
+ }
+ }
+ });
+ } else if (newStatus == CommentStatus.TRASH || newStatus == CommentStatus.SPAM) {
+ mNotesListFragment.setNoteIsHidden(note.getId(), true);
+ // Show undo bar for trash or spam actions
+ new UndoBarController.UndoBar(this)
+ .message(newStatus == CommentStatus.TRASH ? R.string.comment_trashed : R.string.comment_spammed)
+ .listener(new UndoBarController.AdvancedUndoListener() {
+ @Override
+ public void onHide(Parcelable parcelable) {
+ if (isFinishing()) return;
+ // Deleted notifications in Simperium never come back, so we won't
+ // make the request until the undo bar fades away
+ CommentActions.moderateCommentForNote(note, newStatus,
+ new CommentActions.CommentActionListener() {
+ @Override
+ public void onActionResult(boolean succeeded) {
+ if (isFinishing()) return;
+
+ if (!succeeded) {
+ mNotesListFragment.setNoteIsHidden(note.getId(), false);
+ ToastUtils.showToast(NotificationsActivity.this,
+ R.string.error_moderate_comment,
+ ToastUtils.Duration.LONG
+ );
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onClear() {
+ //noop
+ }
+
+ @Override
+ public void onUndo(Parcelable parcelable) {
+ mNotesListFragment.setNoteIsHidden(note.getId(), false);
+ }
+ }).show();
}
+ }
- // swap the fragment
- FragmentTransaction ft = fm.beginTransaction();
- ft.replace(R.id.layout_fragment_container, detailFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
-
- AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATIONS_OPENED_NOTIFICATION_DETAILS);
- // only add to backstack if we're removing the list view from the fragment container
- View container = findViewById(R.id.layout_fragment_container);
- if (container.findViewById(R.id.fragment_notes_list) != null) {
- mMenuDrawer.setDrawerIndicatorEnabled(false);
- ft.addToBackStack(null);
- if (mNotesList != null) {
- ft.hide(mNotesList);
+ @Override
+ public void onCommentChanged(CommentActions.ChangedFrom changedFrom, CommentActions.ChangeType changeType) {
+ // pop back stack if we edited a comment notification, so simperium will show the change
+ if (changedFrom == CommentActions.ChangedFrom.COMMENT_DETAIL
+ && changeType == CommentActions.ChangeType.EDITED) {
+ FragmentManager fm = getFragmentManager();
+ if (fm.getBackStackEntryCount() > 0) {
+ fm.popBackStack();
}
}
- ft.commitAllowingStateLoss();
}
private class NoteClickListener implements NotificationsListFragment.OnNoteClickListener {
@Override
- public void onClickNote(Note note){
- if (note == null)
- return;
+ public void onClickNote(Note note) {
+ if (note == null) return;
+
// open the latest version of this note just in case it has changed - this can
// happen if the note was tapped from the list fragment after it was updated
// by another fragment (such as NotificationCommentLikeFragment)
- //Note updatedNote = WordPress.wpDB.getNoteById(StringUtils.stringToInt(note.getId()));
openNote(note);
}
}
+ // Retrieves comment reply text so we can restore it upon returning to CommentDetailFragment
+ private String getCommentReplyText() {
+ if (mDetailFragment != null && mDetailFragment instanceof CommentDetailFragment) {
+ CommentDetailFragment commentDetailFragment = (CommentDetailFragment)mDetailFragment;
+ if (!TextUtils.isEmpty(commentDetailFragment.getReplyText())) {
+ return commentDetailFragment.getReplyText();
+ }
+ }
+
+ return null;
+ }
+
@Override
- public void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(@Nonnull Bundle outState) {
if (outState.isEmpty()) {
outState.putBoolean("bug_19917_fix", true);
}
outState.putBoolean(KEY_INITIAL_UPDATE, mHasPerformedInitialUpdate);
- outState.putInt(NOTE_ID_EXTRA, mSelectedNoteId);
- if (mSelectedReaderPost != null) {
- outState.putSerializable(KEY_SELECTED_POST_ID, mSelectedReaderPost);
+ if (getFragmentManager().getBackStackEntryCount() > 0
+ || getFragmentManager().findFragmentByTag(TAG_TABLET_DETAIL_VIEW) != null) {
+ outState.putString(NOTE_ID_EXTRA, mSelectedNoteId);
}
- if (mSelectedComment != null) {
- outState.putSerializable(KEY_SELECTED_COMMENT_ID, mSelectedComment);
+
+ // Save text in comment reply EditText
+ if (!TextUtils.isEmpty(getCommentReplyText())) {
+ outState.putString(KEY_REPLY_TEXT, getCommentReplyText());
}
+
+ // Save list view scroll position
+ if (mNotesListFragment != null) {
+ outState.putInt(KEY_LIST_POSITION, mNotesListFragment.getScrollPosition());
+ }
+
super.onSaveInstanceState(outState);
}
@@ -355,44 +443,4 @@ protected void onStart() {
ReaderAuthActions.updateCookies(this);
}
}
-
- @Override
- protected Dialog onCreateDialog(int id) {
- Dialog dialog = CommentDialogs.createCommentDialog(this, id);
- if (dialog != null)
- return dialog;
- return super.onCreateDialog(id);
- }
-
- /**
- * called from fragment when a link to a post is tapped - shows the post in a reader
- * detail fragment
- */
- @Override
- public void onPostClicked(Note note, int remoteBlogId, int postId) {
- mTmpSelectedReaderPost = new BlogPairId(remoteBlogId, postId);
- ReaderPostDetailFragment readerFragment = ReaderPostDetailFragment.newInstance(remoteBlogId, postId);
- String tagForFragment = getString(R.string.fragment_tag_reader_post_detail);
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.replace(R.id.layout_fragment_container, readerFragment, tagForFragment)
- .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
- .addToBackStack(tagForFragment)
- .commit();
- }
-
- /**
- * called from fragment when a link to a comment is tapped - shows the comment in the comment
- * detail fragment
- */
- @Override
- public void onCommentClicked(Note note, int remoteBlogId, long commentId) {
- mTmpSelectedComment = new BlogPairId(remoteBlogId, commentId);
- CommentDetailFragment commentFragment = CommentDetailFragment.newInstance(note);
- String tagForFragment = getString(R.string.fragment_tag_comment_detail);
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.replace(R.id.layout_fragment_container, commentFragment, tagForFragment)
- .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
- .addToBackStack(tagForFragment)
- .commit();
- }
}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsConstants.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsConstants.java
new file mode 100644
index 000000000000..be3e7c176f46
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsConstants.java
@@ -0,0 +1,14 @@
+package org.wordpress.android.ui.notifications;
+
+
+import android.graphics.Color;
+
+public class NotificationsConstants {
+ // These colors are used in NoteBlockClickableSpan.
+ // If those colors are updated there, they should be updated here as well
+ public static final int COLOR_CALYPSO_BLUE_BORDER = Color.parseColor("#d2dee6");
+ public static int COLOR_CALYPSO_DARK_BLUE = Color.parseColor("#324155");
+ public static final int COLOR_NEW_KID_BLUE = Color.parseColor("#2EA2CC");
+ public static final int COLOR_CALYPSO_BLUE = Color.parseColor("#90aec2");
+ public static final int COLOR_CALYPSO_WHITE = Color.parseColor("#FFFFFF");
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java
new file mode 100644
index 000000000000..ac6fe0149503
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java
@@ -0,0 +1,294 @@
+/**
+ * One fragment to rule them all (Notes, that is)
+ */
+package org.wordpress.android.ui.notifications;
+
+import android.app.ListFragment;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.wordpress.android.R;
+import org.wordpress.android.models.CommentStatus;
+import org.wordpress.android.models.Note;
+import org.wordpress.android.ui.notifications.blocks.CommentUserNoteBlock;
+import org.wordpress.android.ui.notifications.blocks.HeaderUserNoteBlock;
+import org.wordpress.android.ui.notifications.blocks.NoteBlock;
+import org.wordpress.android.ui.notifications.blocks.NoteBlockClickableSpan;
+import org.wordpress.android.ui.notifications.blocks.NoteBlockRangeType;
+import org.wordpress.android.ui.notifications.blocks.UserNoteBlock;
+import org.wordpress.android.ui.notifications.utils.NotificationsUtils;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.JSONUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+public class NotificationsDetailListFragment extends ListFragment implements NotificationFragment {
+ private Note mNote;
+ private final List mNoteBlockArray = new ArrayList();
+ private LinearLayout mRootLayout;
+ private ViewGroup mFooterView;
+
+ private int mBackgroundColor;
+ private int mCommentListPosition = ListView.INVALID_POSITION;
+ private CommentUserNoteBlock.OnCommentStatusChangeListener mOnCommentStatusChangeListener;
+
+ public NotificationsDetailListFragment() {
+ }
+
+ public static NotificationsDetailListFragment newInstance(final Note note) {
+ NotificationsDetailListFragment fragment = new NotificationsDetailListFragment();
+ fragment.setNote(note);
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(@Nonnull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.notifications_fragment_detail_list, container, false);
+ mRootLayout = (LinearLayout)view.findViewById(R.id.notifications_list_root);
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle bundle) {
+ super.onActivityCreated(bundle);
+
+ mBackgroundColor = getResources().getColor(R.color.white);
+
+ ListView listView = getListView();
+ listView.setDivider(null);
+ listView.setDividerHeight(0);
+ listView.setHeaderDividersEnabled(false);
+
+ if (mFooterView != null) {
+ listView.addFooterView(mFooterView);
+ }
+
+ reloadNoteBlocks();
+ }
+
+ @Override
+ public Note getNote() {
+ return mNote;
+ }
+
+ @Override
+ public void setNote(Note note) {
+ mNote = note;
+ }
+
+ private void reloadNoteBlocks() {
+ new LoadNoteBlocksTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ public void setFooterView(ViewGroup footerView) {
+ mFooterView = footerView;
+ }
+
+ private class NoteBlockAdapter extends ArrayAdapter {
+
+ private final List mNoteBlockList;
+ private final LayoutInflater mLayoutInflater;
+
+ NoteBlockAdapter(Context context, List noteBlocks) {
+ super(context, 0, noteBlocks);
+
+ mNoteBlockList = noteBlocks;
+ mLayoutInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ NoteBlock noteBlock = mNoteBlockList.get(position);
+
+ // Check the tag for this recycled view, if it matches we can reuse it
+ if (convertView == null || noteBlock.getBlockType() != convertView.getTag(R.id.note_block_tag_id)) {
+ convertView = mLayoutInflater.inflate(noteBlock.getLayoutResourceId(), parent, false);
+ convertView.setTag(noteBlock.getViewHolder(convertView));
+ }
+
+ // Update the block type for this view
+ convertView.setTag(R.id.note_block_tag_id, noteBlock.getBlockType());
+
+ noteBlock.setBackgroundColor(mBackgroundColor);
+
+ return noteBlock.configureView(convertView);
+ }
+ }
+
+ private final NoteBlock.OnNoteBlockTextClickListener mOnNoteBlockTextClickListener = new NoteBlock.OnNoteBlockTextClickListener() {
+ @Override
+ public void onNoteBlockTextClicked(NoteBlockClickableSpan clickedSpan) {
+ if (!isAdded()) return;
+
+ NotificationsUtils.handleNoteBlockSpanClick((NotificationsActivity) getActivity(), clickedSpan);
+ }
+
+ @Override
+ public void showDetailForNoteIds() {
+ if (!isAdded() || mNote == null || !(getActivity() instanceof NotificationsActivity)) {
+ return;
+ }
+
+ NotificationsActivity notificationsActivity = (NotificationsActivity)getActivity();
+ if (mNote.getParentCommentId() > 0 || (!mNote.isCommentType() && mNote.getCommentId() > 0)) {
+ // show comment detail
+ notificationsActivity.showCommentDetailForNote(mNote);
+ } else {
+ // otherwise, load the post in the Reader
+ notificationsActivity.showPostActivity(mNote.getSiteId(), mNote.getPostId());
+ }
+ }
+ };
+
+ private final UserNoteBlock.OnGravatarClickedListener mOnGravatarClickedListener = new UserNoteBlock.OnGravatarClickedListener() {
+ @Override
+ public void onGravatarClicked(long siteId, long userId) {
+ if (!isAdded()) return;
+
+ NotificationsActivity notificationsActivity = (NotificationsActivity)getActivity();
+ notificationsActivity.showBlogPreviewActivity(siteId, null);
+ }
+ };
+
+
+ // Loop through the 'body' items in this note, and create blocks for each.
+ private class LoadNoteBlocksTask extends AsyncTask {
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ if (mNote == null) return false;
+
+ JSONArray bodyArray = mNote.getBody();
+ mNoteBlockArray.clear();
+
+ // Add the note header if one was provided
+ if (mNote.getHeader() != null) {
+ HeaderUserNoteBlock headerNoteBlock = new HeaderUserNoteBlock(
+ getActivity(),
+ mNote.getHeader(),
+ mOnNoteBlockTextClickListener,
+ mOnGravatarClickedListener
+ );
+
+ headerNoteBlock.setIsComment(mNote.isCommentType());
+ mNoteBlockArray.add(headerNoteBlock);
+ }
+
+ boolean isBadgeView = false;
+ if (bodyArray != null && bodyArray.length() > 0) {
+ for (int i=0; i < bodyArray.length(); i++) {
+ try {
+ JSONObject noteObject = bodyArray.getJSONObject(i);
+ // Determine NoteBlock type and add it to the array
+ NoteBlock noteBlock;
+ String noteBlockTypeString = JSONUtil.queryJSON(noteObject, "type", "");
+
+ if (NoteBlockRangeType.fromString(noteBlockTypeString) == NoteBlockRangeType.USER) {
+ if (mNote.isCommentType()) {
+ // Set comment position so we can target it later
+ // See refreshBlocksForCommentStatus()
+ mCommentListPosition = i + mNoteBlockArray.size();
+
+ // We'll snag the next body array item for comment user blocks
+ if (i + 1 < bodyArray.length()) {
+ JSONObject commentTextBlock = bodyArray.getJSONObject(i + 1);
+ noteObject.put("comment_text", commentTextBlock);
+ i++;
+ }
+
+ // Add timestamp to block for display
+ noteObject.put("timestamp", mNote.getTimestamp());
+
+ noteBlock = new CommentUserNoteBlock(
+ getActivity(),
+ noteObject,
+ mOnNoteBlockTextClickListener,
+ mOnGravatarClickedListener
+ );
+
+ // Set listener for comment status changes, so we can update bg and text colors
+ CommentUserNoteBlock commentUserNoteBlock = (CommentUserNoteBlock)noteBlock;
+ mOnCommentStatusChangeListener = commentUserNoteBlock.getOnCommentChangeListener();
+ commentUserNoteBlock.setCommentStatus(mNote.getCommentStatus());
+ commentUserNoteBlock.configureResources(getActivity());
+ } else {
+ noteBlock = new UserNoteBlock(
+ getActivity(),
+ noteObject,
+ mOnNoteBlockTextClickListener,
+ mOnGravatarClickedListener
+ );
+ }
+ } else {
+ noteBlock = new NoteBlock(noteObject, mOnNoteBlockTextClickListener);
+ }
+
+ // Badge notifications apply different colors and formatting
+ if (isAdded() && noteBlock.containsBadgeMediaType()) {
+ isBadgeView = true;
+ mBackgroundColor = getActivity().getResources().getColor(R.color.transparent);
+ }
+
+ if (isBadgeView) {
+ noteBlock.setIsBadge();
+ }
+
+ mNoteBlockArray.add(noteBlock);
+ } catch (JSONException e) {
+ AppLog.e(AppLog.T.NOTIFS, "Invalid note data, could not parse.");
+ }
+ }
+ }
+
+ return isBadgeView;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean isBadgeView) {
+ if (!isAdded()) return;
+
+ if (isBadgeView) {
+ mRootLayout.setGravity(Gravity.CENTER_VERTICAL);
+ }
+
+ setListAdapter(new NoteBlockAdapter(getActivity(), mNoteBlockArray));
+ }
+ }
+
+ public void refreshBlocksForCommentStatus(CommentStatus newStatus) {
+ if (mOnCommentStatusChangeListener != null) {
+ mOnCommentStatusChangeListener.onCommentStatusChanged(newStatus);
+ ListView listView = getListView();
+ if (listView == null || mCommentListPosition == ListView.INVALID_POSITION) {
+ return;
+ }
+
+ // Redraw the comment row if it is visible so that the background and text colors update
+ // See: http://stackoverflow.com/questions/4075975/redraw-a-single-row-in-a-listview/9987616#9987616
+ int firstPosition = listView.getFirstVisiblePosition();
+ int endPosition = listView.getLastVisiblePosition();
+ for (int i = firstPosition; i < endPosition; i++) {
+ if (mCommentListPosition == i) {
+ View view = listView.getChildAt(i - firstPosition);
+ listView.getAdapter().getView(i, view, listView);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.java
index 21e24342b215..b6fb1c0b2cc0 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.java
@@ -1,7 +1,6 @@
package org.wordpress.android.ui.notifications;
import android.app.ListFragment;
-import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
@@ -17,18 +16,22 @@
import org.wordpress.android.R;
import org.wordpress.android.models.Note;
+import org.wordpress.android.ui.notifications.utils.SimperiumUtils;
import org.wordpress.android.util.ToastUtils;
import org.wordpress.android.util.ptr.PullToRefreshHelper;
+import javax.annotation.Nonnull;
+
import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout;
public class NotificationsListFragment extends ListFragment implements Bucket.Listener {
private PullToRefreshHelper mFauxPullToRefreshHelper;
private NotesAdapter mNotesAdapter;
private OnNoteClickListener mNoteClickListener;
- private boolean mShouldLoadFirstNote;
- Bucket mBucket;
+ private int mRestoredListPosition;
+
+ private Bucket mBucket;
/**
* For responding to tapping of notes
@@ -38,29 +41,34 @@ public interface OnNoteClickListener {
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.empty_listview, container, false);
- return v;
+ public View onCreateView(@Nonnull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.notifications_fragment_notes_list, container, false);
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ initPullToRefreshHelper();
+ mFauxPullToRefreshHelper.registerReceiver(getActivity());
// setup the initial notes adapter, starts listening to the bucket
mBucket = SimperiumUtils.getNotesBucket();
+ if (mBucket == null) {
+ ToastUtils.showToast(getActivity(), R.string.error_refresh_notifications);
+ return;
+ }
ListView listView = getListView();
- listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
- listView.setDivider(getResources().getDrawable(R.drawable.list_divider));
- listView.setDividerHeight(1);
- if (mBucket != null) {
+ listView.setDivider(null);
+ listView.setDividerHeight(0);
+
+ if (mNotesAdapter == null) {
mNotesAdapter = new NotesAdapter(getActivity(), mBucket);
- setListAdapter(mNotesAdapter);
- } else {
- ToastUtils.showToast(getActivity(), R.string.error_refresh_notifications);
}
+ setListAdapter(mNotesAdapter);
+
// Set empty text if no notifications
TextView textview = (TextView) listView.getEmptyView();
if (textview != null) {
@@ -68,56 +76,32 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
}
}
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- initPullToRefreshHelper();
- mFauxPullToRefreshHelper.registerReceiver(getActivity());
- }
-
@Override
public void onResume() {
super.onResume();
refreshNotes();
// start listening to bucket change events
- if (mBucket != null) {
- mBucket.addListener(this);
- }
+ mBucket.addListener(this);
}
@Override
public void onPause() {
- // unregister the listener and close the cursor
- if (mBucket != null) {
- mBucket.removeListener(this);
- }
+ // unregister the listener
+ mBucket.removeListener(this);
+
super.onPause();
}
@Override
- public void onDestroyView() {
+ public void onDestroy() {
+ // Close Simperium cursor
+ mNotesAdapter.closeCursor();
+
mFauxPullToRefreshHelper.unregisterReceiver(getActivity());
super.onDestroyView();
}
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- boolean isRefreshing = mFauxPullToRefreshHelper.isRefreshing();
- super.onConfigurationChanged(newConfig);
- // Pull to refresh layout is destroyed onDetachedFromWindow,
- // so we have to re-init the layout, via the helper here
- initPullToRefreshHelper();
- mFauxPullToRefreshHelper.setRefreshing(isRefreshing);
- }
-
- public void closeAdapterCursor() {
- if (mNotesAdapter != null) {
- mNotesAdapter.closeCursor();
- }
- }
-
private void initPullToRefreshHelper() {
mFauxPullToRefreshHelper = new PullToRefreshHelper(
getActivity(),
@@ -141,9 +125,15 @@ public void run() {
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
+ if (!isAdded()) return;
+
Note note = mNotesAdapter.getNote(position);
- l.setItemChecked(position, true);
- if (note != null && mNoteClickListener != null) {
+
+ if (mNotesAdapter.isModeratingNote(note.getId())) {
+ return;
+ }
+
+ if (mNoteClickListener != null) {
mNoteClickListener.onClickNote(note);
}
}
@@ -152,21 +142,43 @@ public void setOnNoteClickListener(OnNoteClickListener listener) {
mNoteClickListener = listener;
}
- protected void updateLastSeenTime() {
+ public void setNoteIsHidden(String noteId, boolean isHidden) {
+ if (mNotesAdapter == null) return;
+
+ if (isHidden) {
+ mNotesAdapter.addHiddenNoteId(noteId);
+ } else {
+ mNotesAdapter.removeHiddenNoteId(noteId);
+ }
+ }
+
+ public void setNoteIsModerating(String noteId, boolean isModerating) {
+ if (mNotesAdapter == null) return;
+
+ if (isModerating) {
+ mNotesAdapter.addModeratingNoteId(noteId);
+ } else {
+ mNotesAdapter.removeModeratingNoteId(noteId);
+ }
+ }
+
+ void updateLastSeenTime() {
// set the timestamp to now
try {
if (mNotesAdapter != null && mNotesAdapter.getCount() > 0 && SimperiumUtils.getMetaBucket() != null) {
Note newestNote = mNotesAdapter.getNote(0);
BucketObject meta = SimperiumUtils.getMetaBucket().get("meta");
- meta.setProperty("last_seen", newestNote.getTimestamp());
- meta.save();
+ if (meta != null && newestNote != null) {
+ meta.setProperty("last_seen", newestNote.getTimestamp());
+ meta.save();
+ }
}
} catch (BucketObjectMissingException e) {
// try again later, meta is created by wordpress.com
}
}
- public void refreshNotes() {
+ void refreshNotes() {
if (!isAdded() || mNotesAdapter == null) {
return;
}
@@ -177,27 +189,41 @@ public void run() {
mNotesAdapter.reloadNotes();
updateLastSeenTime();
- // Show first note if we're on a landscape tablet
- if (mShouldLoadFirstNote && mNotesAdapter.getCount() > 0) {
- mShouldLoadFirstNote = false;
- Note note = mNotesAdapter.getNote(0);
- if (note != null && mNoteClickListener != null) {
- mNoteClickListener.onClickNote(note);
- getListView().setItemChecked(0, true);
- }
- }
+ restoreListScrollPosition();
}
});
}
+ private void restoreListScrollPosition() {
+ if (getListView() != null && mRestoredListPosition != ListView.INVALID_POSITION
+ && mRestoredListPosition < mNotesAdapter.getCount()) {
+ // Restore scroll position in list
+ getListView().setSelectionFromTop(mRestoredListPosition, 0);
+ mRestoredListPosition = ListView.INVALID_POSITION;
+ }
+ }
+
@Override
public void onSaveInstanceState(Bundle outState) {
if (outState.isEmpty()) {
outState.putBoolean("bug_19917_fix", true);
}
+
super.onSaveInstanceState(outState);
}
+ public int getScrollPosition() {
+ if (!isAdded() || getListView() == null) {
+ return ListView.INVALID_POSITION;
+ }
+
+ return getListView().getFirstVisiblePosition();
+ }
+
+ public void setRestoredListPosition(int listPosition) {
+ mRestoredListPosition = listPosition;
+ }
+
/**
* Simperium bucket listener methods
*/
@@ -220,8 +246,4 @@ public void onChange(Bucket bucket, Bucket.ChangeType type, String key) {
public void onBeforeUpdateObject(Bucket noteBucket, Note note) {
//noop
}
-
- public void setShouldLoadFirstNote(boolean shouldLoad) {
- mShouldLoadFirstNote = shouldLoad;
- }
-}
\ No newline at end of file
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsWebViewActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsWebViewActivity.java
index 19d840a0681f..87d8157317b8 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsWebViewActivity.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsWebViewActivity.java
@@ -14,7 +14,7 @@
@SuppressLint("SetJavaScriptEnabled")
public class NotificationsWebViewActivity extends AuthenticatedWebViewActivity {
- private static final String URL_TO_LOAD = "external_url";
+ public static final String URL_TO_LOAD = "external_url";
public static void openUrl(Context context, String url) {
if (context == null || TextUtils.isEmpty(url))
@@ -29,6 +29,8 @@ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setDisplayZoomControls(false);
+ mWebView.getSettings().setDomStorageEnabled(true);
+
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/BlockType.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/BlockType.java
new file mode 100644
index 000000000000..fc95f9869863
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/BlockType.java
@@ -0,0 +1,12 @@
+package org.wordpress.android.ui.notifications.blocks;
+
+/** BlockTypes that we know about
+ * Unknown blocks will still be displayed using the rules for BASIC blocks
+ */
+public enum BlockType {
+ UNKNOWN,
+ BASIC,
+ USER,
+ USER_HEADER,
+ USER_COMMENT
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/CommentUserNoteBlock.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/CommentUserNoteBlock.java
new file mode 100644
index 000000000000..7d43f08372ab
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/CommentUserNoteBlock.java
@@ -0,0 +1,194 @@
+package org.wordpress.android.ui.notifications.blocks;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.volley.toolbox.NetworkImageView;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.CommentStatus;
+import org.wordpress.android.ui.comments.CommentUtils;
+import org.wordpress.android.ui.notifications.utils.NotificationsUtils;
+import org.wordpress.android.util.DateTimeUtils;
+import org.wordpress.android.util.PhotonUtils;
+
+// A user block with slightly different formatting for display in a comment detail
+public class CommentUserNoteBlock extends UserNoteBlock {
+
+ private CommentStatus mCommentStatus = CommentStatus.UNKNOWN;
+ private int mTextViewIndent;
+ private int mNormalBackgroundColor;
+ private int mNormalTextColor;
+ private int mAgoTextColor;
+ private int mUnapprovedTextColor;
+ private int mIndentedLeftPadding;
+
+ private boolean mStatusChanged;
+
+ public interface OnCommentStatusChangeListener {
+ public void onCommentStatusChanged(CommentStatus newStatus);
+ }
+
+ public CommentUserNoteBlock(Context context, JSONObject noteObject,
+ OnNoteBlockTextClickListener onNoteBlockTextClickListener,
+ OnGravatarClickedListener onGravatarClickedListener) {
+ super(context, noteObject, onNoteBlockTextClickListener, onGravatarClickedListener);
+
+ if (context != null) {
+ setAvatarSize(context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_small));
+ }
+ }
+
+ @Override
+ public BlockType getBlockType() {
+ return BlockType.USER_COMMENT;
+ }
+
+ @Override
+ public int getLayoutResourceId() {
+ return R.layout.note_block_comment_user;
+ }
+
+ @Override
+ public View configureView(View view) {
+ final CommentUserNoteBlockHolder noteBlockHolder = (CommentUserNoteBlockHolder)view.getTag();
+
+ noteBlockHolder.nameTextView.setText(getNoteText().toString());
+ noteBlockHolder.agoTextView.setText(DateTimeUtils.timestampToTimeSpan(getTimestamp()));
+
+ if (hasImageMediaItem()) {
+ String imageUrl = PhotonUtils.fixAvatar(getNoteMediaItem().optString("url", ""), getAvatarSize());
+ noteBlockHolder.avatarImageView.setImageUrl(imageUrl, WordPress.imageLoader);
+ if (!TextUtils.isEmpty(getUserUrl())) {
+ noteBlockHolder.avatarImageView.setOnTouchListener(mOnGravatarTouchListener);
+ } else {
+ noteBlockHolder.avatarImageView.setOnTouchListener(null);
+ }
+ } else {
+ noteBlockHolder.avatarImageView.setImageResource(R.drawable.placeholder);
+ noteBlockHolder.avatarImageView.setOnTouchListener(null);
+ }
+
+ CommentUtils.indentTextViewFirstLine(
+ noteBlockHolder.commentTextView,
+ NotificationsUtils.getSpannableTextFromIndices(getNoteData().optJSONObject("comment_text"), getOnNoteBlockTextClickListener()),
+ mTextViewIndent
+ );
+
+ // Change display based on comment status and type:
+ // 1. Comment replies are indented and have a 'pipe' background
+ // 2. Unapproved comments have different background and text color
+ int paddingLeft = view.getPaddingLeft();
+ int paddingTop = view.getPaddingTop();
+ int paddingRight = view.getPaddingRight();
+ int paddingBottom = view.getPaddingBottom();
+ if (mCommentStatus == CommentStatus.UNAPPROVED) {
+ if (hasCommentNestingLevel()) {
+ paddingLeft = mIndentedLeftPadding;
+ view.setBackgroundResource(R.drawable.comment_reply_unapproved_background);
+ } else {
+ view.setBackgroundResource(R.drawable.comment_unapproved_background);
+ }
+
+ noteBlockHolder.dividerView.setVisibility(View.INVISIBLE);
+
+ noteBlockHolder.agoTextView.setTextColor(mUnapprovedTextColor);
+ noteBlockHolder.nameTextView.setTextColor(mUnapprovedTextColor);
+ noteBlockHolder.commentTextView.setTextColor(mUnapprovedTextColor);
+ } else {
+ if (hasCommentNestingLevel()) {
+ paddingLeft = mIndentedLeftPadding;
+ view.setBackgroundResource(R.drawable.comment_reply_background);
+ noteBlockHolder.dividerView.setVisibility(View.INVISIBLE);
+ } else {
+ view.setBackgroundColor(mNormalBackgroundColor);
+ noteBlockHolder.dividerView.setVisibility(View.VISIBLE);
+ }
+
+ noteBlockHolder.agoTextView.setTextColor(mAgoTextColor);
+ noteBlockHolder.nameTextView.setTextColor(mNormalTextColor);
+ noteBlockHolder.commentTextView.setTextColor(mNormalTextColor);
+ }
+
+ view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
+
+ // If status was changed, fade in the view
+ if (mStatusChanged) {
+ mStatusChanged = false;
+ view.setAlpha(0.4f);
+ view.animate().alpha(1.0f).start();
+ }
+
+ return view;
+ }
+
+ private long getTimestamp() {
+ return getNoteData().optInt("timestamp", 0);
+ }
+
+ private boolean hasCommentNestingLevel() {
+ try {
+ JSONObject commentTextObject = getNoteData().getJSONObject("comment_text");
+ return commentTextObject.optInt("nest_level", 0) > 0;
+ } catch (JSONException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public Object getViewHolder(View view) {
+ return new CommentUserNoteBlockHolder(view);
+ }
+
+ private class CommentUserNoteBlockHolder {
+ private final NetworkImageView avatarImageView;
+ private final TextView nameTextView;
+ private final TextView agoTextView;
+ private final TextView commentTextView;
+ private final View dividerView;
+
+ public CommentUserNoteBlockHolder(View view) {
+ nameTextView = (TextView)view.findViewById(R.id.user_name);
+ agoTextView = (TextView)view.findViewById(R.id.user_comment_ago);
+ agoTextView.setVisibility(View.VISIBLE);
+ commentTextView = (TextView)view.findViewById(R.id.user_comment);
+ commentTextView.setMovementMethod(new NoteBlockLinkMovementMethod());
+ avatarImageView = (NetworkImageView)view.findViewById(R.id.user_avatar);
+ dividerView = view.findViewById(R.id.divider_view);
+ }
+ }
+
+ public void configureResources(Context context) {
+ if (context == null) return;
+
+ mNormalTextColor = context.getResources().getColor(R.color.calypso_blue_dark);
+ mNormalBackgroundColor = context.getResources().getColor(R.color.white);
+ mAgoTextColor = context.getResources().getColor(R.color.calypso_blue);
+ mUnapprovedTextColor = context.getResources().getColor(R.color.calypso_orange_dark);
+ mTextViewIndent = context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_small) +
+ context.getResources().getDimensionPixelSize(R.dimen.notifications_adjusted_font_margin);
+ // Double margin_extra_large for increased indent in comment replies
+ mIndentedLeftPadding = context.getResources().getDimensionPixelSize(R.dimen.margin_extra_large) * 2;
+ }
+
+ private final OnCommentStatusChangeListener mOnCommentChangedListener = new OnCommentStatusChangeListener() {
+ @Override
+ public void onCommentStatusChanged(CommentStatus newStatus) {
+ mCommentStatus = newStatus;
+ mStatusChanged = true;
+ }
+ };
+
+ public void setCommentStatus(CommentStatus status) {
+ mCommentStatus = status;
+ }
+
+ public OnCommentStatusChangeListener getOnCommentChangeListener() {
+ return mOnCommentChangedListener;
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/HeaderUserNoteBlock.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/HeaderUserNoteBlock.java
new file mode 100644
index 000000000000..2d28e150d15d
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/HeaderUserNoteBlock.java
@@ -0,0 +1,158 @@
+package org.wordpress.android.ui.notifications.blocks;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.TextView;
+
+import com.android.volley.toolbox.NetworkImageView;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.util.JSONUtil;
+import org.wordpress.android.util.PhotonUtils;
+
+// Note header, displayed at top of detail view
+public class HeaderUserNoteBlock extends NoteBlock {
+
+ private final JSONArray mHeaderArray;
+
+ private final UserNoteBlock.OnGravatarClickedListener mGravatarClickedListener;
+ private Boolean mIsComment;
+ private int mAvatarSize;
+
+ public HeaderUserNoteBlock(Context context, JSONArray headerArray,
+ OnNoteBlockTextClickListener onNoteBlockTextClickListener,
+ UserNoteBlock.OnGravatarClickedListener onGravatarClickedListener) {
+ super(new JSONObject(), onNoteBlockTextClickListener);
+
+ mHeaderArray = headerArray;
+ mGravatarClickedListener = onGravatarClickedListener;
+
+ if (context != null) {
+ mAvatarSize = context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_small);
+ }
+ }
+
+ @Override
+ public BlockType getBlockType() {
+ return BlockType.USER_HEADER;
+ }
+
+ public int getLayoutResourceId() {
+ return R.layout.note_block_user_header;
+ }
+
+ @Override
+ public View configureView(View view) {
+ final NoteHeaderBlockHolder noteBlockHolder = (NoteHeaderBlockHolder)view.getTag();
+
+ noteBlockHolder.nameTextView.setText(getUserName());
+ noteBlockHolder.avatarImageView.setImageUrl(getAvatarUrl(), WordPress.imageLoader);
+ if (!TextUtils.isEmpty(getUserUrl())) {
+ noteBlockHolder.avatarImageView.setOnTouchListener(mOnGravatarTouchListener);
+ } else {
+ noteBlockHolder.avatarImageView.setOnTouchListener(null);
+ }
+
+ noteBlockHolder.snippetTextView.setText(getSnippet());
+
+ if (mIsComment) {
+ View footerView = view.findViewById(R.id.header_footer);
+ View footerCommentView = view.findViewById(R.id.header_footer_comment);
+ footerView.setVisibility(View.GONE);
+ footerCommentView.setVisibility(View.VISIBLE);
+ }
+
+ return view;
+ }
+
+ private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (getOnNoteBlockTextClickListener() != null) {
+ getOnNoteBlockTextClickListener().showDetailForNoteIds();
+ }
+ }
+ };
+
+ private String getUserName() {
+ return JSONUtil.queryJSON(mHeaderArray, "[0].text", "");
+ }
+
+ private String getAvatarUrl() {
+ return PhotonUtils.fixAvatar(JSONUtil.queryJSON(mHeaderArray, "[0].media[0].url", ""), mAvatarSize);
+ }
+
+ private String getUserUrl() {
+ return JSONUtil.queryJSON(mHeaderArray, "[0].ranges[0].url", "");
+ }
+
+ private String getSnippet() {
+ return JSONUtil.queryJSON(mHeaderArray, "[1].text", "");
+ }
+
+ @Override
+ public Object getViewHolder(View view) {
+ return new NoteHeaderBlockHolder(view);
+ }
+
+ public void setIsComment(Boolean isComment) {
+ mIsComment = isComment;
+ }
+
+ private class NoteHeaderBlockHolder {
+ private final TextView nameTextView;
+ private final TextView snippetTextView;
+ private final NetworkImageView avatarImageView;
+
+ public NoteHeaderBlockHolder(View view) {
+ View rootView = view.findViewById(R.id.header_root_view);
+ rootView.setOnClickListener(mOnClickListener);
+ nameTextView = (TextView)view.findViewById(R.id.header_user);
+ snippetTextView = (TextView)view.findViewById(R.id.header_snippet);
+ avatarImageView = (NetworkImageView)view.findViewById(R.id.header_avatar);
+ }
+ }
+
+ private final View.OnTouchListener mOnGravatarTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+
+ int animationDuration = 150;
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ v.animate()
+ .scaleX(0.9f)
+ .scaleY(0.9f)
+ .alpha(0.5f)
+ .setDuration(animationDuration)
+ .setInterpolator(new DecelerateInterpolator());
+ } else if (event.getActionMasked() == MotionEvent.ACTION_UP
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+ v.animate()
+ .scaleX(1.0f)
+ .scaleY(1.0f)
+ .alpha(1.0f)
+ .setDuration(animationDuration)
+ .setInterpolator(new DecelerateInterpolator());
+
+ if (event.getActionMasked() == MotionEvent.ACTION_UP && mGravatarClickedListener != null) {
+ // Fire the listener, which will load the site preview for the user's site
+ // In the future we can use this to load a 'profile view' (currently in R&D)
+ long siteId = Long.valueOf(JSONUtil.queryJSON(mHeaderArray, "[0].ranges[0].site_id", 0));
+ long userId = Long.valueOf(JSONUtil.queryJSON(mHeaderArray, "[0].ranges[0].id", 0));
+ if (siteId > 0 && userId > 0) {
+ mGravatarClickedListener.onGravatarClicked(siteId, userId);
+ }
+ }
+ }
+
+ return true;
+ }
+ };
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlock.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlock.java
new file mode 100644
index 000000000000..5902c3edcd7c
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlock.java
@@ -0,0 +1,257 @@
+package org.wordpress.android.ui.notifications.blocks;
+
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.MediaController;
+import android.widget.VideoView;
+
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.ImageLoader;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.ui.notifications.utils.NotificationsUtils;
+import org.wordpress.android.util.DisplayUtils;
+import org.wordpress.android.util.JSONUtil;
+import org.wordpress.android.widgets.WPTextView;
+
+/**
+ * A block of data displayed in a notification.
+ * This basic block can support a media item (image/video) and/or text.
+ */
+public class NoteBlock {
+
+ private static final String PROPERTY_MEDIA_TYPE = "type";
+ private static final String PROPERTY_MEDIA_URL = "url";
+
+ private final JSONObject mNoteData;
+ private final OnNoteBlockTextClickListener mOnNoteBlockTextClickListener;
+ private JSONObject mMediaItem;
+ private boolean mIsBadge;
+ private boolean mHasAnimatedBadge;
+ private int mBackgroundColor;
+
+ public interface OnNoteBlockTextClickListener {
+ public void onNoteBlockTextClicked(NoteBlockClickableSpan clickedSpan);
+ public void showDetailForNoteIds();
+ }
+
+ public NoteBlock(JSONObject noteObject, OnNoteBlockTextClickListener onNoteBlockTextClickListener) {
+ mNoteData = noteObject;
+ mOnNoteBlockTextClickListener = onNoteBlockTextClickListener;
+ }
+
+ OnNoteBlockTextClickListener getOnNoteBlockTextClickListener() {
+ return mOnNoteBlockTextClickListener;
+ }
+
+ public BlockType getBlockType() {
+ return BlockType.BASIC;
+ }
+
+ JSONObject getNoteData() {
+ return mNoteData;
+ }
+
+ Spannable getNoteText() {
+ return NotificationsUtils.getSpannableTextFromIndices(mNoteData, mOnNoteBlockTextClickListener);
+ }
+
+ JSONObject getNoteMediaItem() {
+ if (mMediaItem == null) {
+ mMediaItem = JSONUtil.queryJSON(mNoteData, "media[0]", new JSONObject());
+ }
+
+ return mMediaItem;
+ }
+
+ public void setIsBadge() {
+ mIsBadge = true;
+ }
+
+ public void setBackgroundColor(int backgroundColor) {
+ mBackgroundColor = backgroundColor;
+ }
+
+ public int getLayoutResourceId() {
+ return R.layout.note_block_basic;
+ }
+
+ private boolean hasMediaArray() {
+ return mNoteData.has("media");
+ }
+
+ boolean hasImageMediaItem() {
+ String mediaType = getNoteMediaItem().optString(PROPERTY_MEDIA_TYPE, "");
+ return hasMediaArray() &&
+ (mediaType.startsWith("image") || mediaType.equals("badge")) &&
+ getNoteMediaItem().has(PROPERTY_MEDIA_URL);
+ }
+
+ boolean hasVideoMediaItem() {
+ return hasMediaArray() &&
+ getNoteMediaItem().optString(PROPERTY_MEDIA_TYPE, "").startsWith("video") &&
+ getNoteMediaItem().has(PROPERTY_MEDIA_URL);
+ }
+
+ public boolean containsBadgeMediaType() {
+ try {
+ JSONArray mediaArray = mNoteData.getJSONArray("media");
+ for (int i=0; i < mediaArray.length(); i++) {
+ JSONObject mediaObject = mediaArray.getJSONObject(i);
+ if (mediaObject.optString(PROPERTY_MEDIA_TYPE, "").equals("badge")) {
+ return true;
+ }
+ }
+ } catch (JSONException e) {
+ return false;
+ }
+
+ return false;
+ }
+
+ public View configureView(final View view) {
+ final BasicNoteBlockHolder noteBlockHolder = (BasicNoteBlockHolder)view.getTag();
+
+ // Note image
+ if (hasImageMediaItem()) {
+ // Request image, and animate it when loaded
+ noteBlockHolder.getImageView().setVisibility(View.VISIBLE);
+ WordPress.imageLoader.get(getNoteMediaItem().optString("url", ""), new ImageLoader.ImageListener() {
+ @Override
+ public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
+ if (!mHasAnimatedBadge && response.getBitmap() != null && view.getContext() != null) {
+ mHasAnimatedBadge = true;
+ noteBlockHolder.getImageView().setImageBitmap(response.getBitmap());
+ Animation pop = AnimationUtils.loadAnimation(view.getContext(), R.anim.pop);
+ noteBlockHolder.getImageView().startAnimation(pop);
+ noteBlockHolder.getImageView().setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ noteBlockHolder.hideImageView();
+ }
+ });
+ } else {
+ noteBlockHolder.hideImageView();
+ }
+
+ // Note video
+ if (hasVideoMediaItem()) {
+ noteBlockHolder.getVideoView().setVideoURI(Uri.parse(getNoteMediaItem().optString("url", "")));
+ noteBlockHolder.getVideoView().setVisibility(View.VISIBLE);
+ } else {
+ noteBlockHolder.hideVideoView();
+ }
+
+ // Note text
+ if (!TextUtils.isEmpty(getNoteText())) {
+ if (mIsBadge) {
+ noteBlockHolder.getTextView().setGravity(Gravity.CENTER_HORIZONTAL);
+ noteBlockHolder.getTextView().setPadding(0, DisplayUtils.dpToPx(view.getContext(), 8), 0, 0);
+ } else {
+ noteBlockHolder.getTextView().setGravity(Gravity.NO_GRAVITY);
+ noteBlockHolder.getTextView().setPadding(0, 0, 0, 0);
+ }
+ noteBlockHolder.getTextView().setText(getNoteText());
+ noteBlockHolder.getTextView().setVisibility(View.VISIBLE);
+ } else {
+ noteBlockHolder.getTextView().setVisibility(View.GONE);
+ }
+
+ view.setBackgroundColor(mBackgroundColor);
+
+ return view;
+ }
+
+ public Object getViewHolder(View view) {
+ return new BasicNoteBlockHolder(view);
+ }
+
+ static class BasicNoteBlockHolder {
+ private final LinearLayout mRootLayout;
+ private final WPTextView mTextView;
+
+ private ImageView mImageView;
+ private VideoView mVideoView;
+
+ BasicNoteBlockHolder(View view) {
+ mRootLayout = (LinearLayout)view;
+ mTextView = (WPTextView) view.findViewById(R.id.note_text);
+ mTextView.setMovementMethod(new NoteBlockLinkMovementMethod());
+ }
+
+ public WPTextView getTextView() {
+ return mTextView;
+ }
+
+ public ImageView getImageView() {
+ if (mImageView == null) {
+ mImageView = new ImageView(mRootLayout.getContext());
+ int imageSize = DisplayUtils.dpToPx(mRootLayout.getContext(), 180);
+ int imagePadding = mRootLayout.getContext().getResources().getDimensionPixelSize(R.dimen.margin_large);
+ LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(imageSize, imageSize);
+ layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
+ mImageView.setLayoutParams(layoutParams);
+ mImageView.setPadding(0, imagePadding, 0, 0);
+ mRootLayout.addView(mImageView, 0);
+ }
+
+ return mImageView;
+ }
+
+ public VideoView getVideoView() {
+ if (mVideoView == null) {
+ mVideoView = new VideoView(mRootLayout.getContext());
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ DisplayUtils.dpToPx(mRootLayout.getContext(), 220));
+ mVideoView.setLayoutParams(layoutParams);
+ mRootLayout.addView(mVideoView, 0);
+
+ // Attach a mediaController if we are displaying a video.
+ final MediaController mediaController = new MediaController(mRootLayout.getContext());
+ mediaController.setMediaPlayer(mVideoView);
+
+ mVideoView.setMediaController(mediaController);
+ mediaController.requestFocus();
+ mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ // Show the media controls when the video is ready to be played.
+ mediaController.show(0);
+ }
+ });
+ }
+
+ return mVideoView;
+ }
+
+ public void hideImageView() {
+ if (mImageView != null) {
+ mImageView.setVisibility(View.GONE);
+ }
+ }
+
+ public void hideVideoView() {
+ if (mVideoView != null) {
+ mVideoView.setVisibility(View.GONE);
+ }
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java
new file mode 100644
index 000000000000..087e2f4f3865
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java
@@ -0,0 +1,131 @@
+package org.wordpress.android.ui.notifications.blocks;
+
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.ClickableSpan;
+import android.view.View;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.wordpress.android.ui.notifications.NotificationsConstants;
+import org.wordpress.android.util.JSONUtil;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A clickable span that includes extra ids/urls
+ * Maps to a 'range' in a WordPress.com note object
+ */
+public class NoteBlockClickableSpan extends ClickableSpan {
+ private long mId;
+ private long mSiteId;
+ private long mPostId;
+ private NoteBlockRangeType mRangeType;
+ private String mUrl;
+ private int[] mIndices;
+ private boolean mPressed;
+ private boolean mShouldLink;
+
+ private int mTextColor = NotificationsConstants.COLOR_CALYPSO_DARK_BLUE;
+
+ private final JSONObject mBlockData;
+
+ public NoteBlockClickableSpan(JSONObject idData, boolean shouldLink) {
+ mBlockData = idData;
+ mShouldLink = shouldLink;
+ processRangeData();
+ }
+
+
+ private void processRangeData() {
+ if (mBlockData != null) {
+ mId = JSONUtil.queryJSON(mBlockData, "id", 0);
+ mSiteId = JSONUtil.queryJSON(mBlockData, "site_id", 0);
+ mPostId = JSONUtil.queryJSON(mBlockData, "post_id", 0);
+ mRangeType = NoteBlockRangeType.fromString(JSONUtil.queryJSON(mBlockData, "type", ""));
+ mUrl = JSONUtil.queryJSON(mBlockData, "url", "");
+ mIndices = new int[]{0,0};
+ JSONArray indicesArray = mBlockData.optJSONArray("indices");
+ if (indicesArray != null) {
+ mIndices[0] = indicesArray.optInt(0);
+ mIndices[1] = indicesArray.optInt(1);
+ }
+
+ // Don't link ranges that we don't know the type of, unless we have a URL
+ mShouldLink = mShouldLink && (mRangeType != NoteBlockRangeType.UNKNOWN || !TextUtils.isEmpty(mUrl));
+
+ // Apply different coloring for blockquotes
+ if (getRangeType() == NoteBlockRangeType.BLOCKQUOTE) {
+ mShouldLink = false;
+ mTextColor = NotificationsConstants.COLOR_CALYPSO_BLUE;
+ }
+ }
+ }
+
+ @Override
+ public void updateDrawState(@Nonnull TextPaint textPaint) {
+ // Set background color
+ textPaint.bgColor = mPressed && !isBlockquoteType() ? NotificationsConstants.COLOR_CALYPSO_BLUE_BORDER : Color.TRANSPARENT;
+ textPaint.setColor(mShouldLink ? NotificationsConstants.COLOR_NEW_KID_BLUE : mTextColor);
+ // No underlines
+ textPaint.setUnderlineText(false);
+ }
+
+ private boolean isBlockquoteType() {
+ return getRangeType() == NoteBlockRangeType.BLOCKQUOTE;
+ }
+
+ // return the desired style for this id type
+ public int getSpanStyle() {
+ switch (getRangeType()) {
+ case USER:
+ return Typeface.BOLD;
+ case SITE:
+ case POST:
+ case COMMENT:
+ case BLOCKQUOTE:
+ return Typeface.ITALIC;
+ default:
+ return Typeface.NORMAL;
+ }
+ }
+
+ @Override
+ public void onClick(View widget) {
+ // noop
+ }
+
+ public NoteBlockRangeType getRangeType() {
+ return mRangeType;
+ }
+
+ public int[] getIndices() {
+ return mIndices;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public long getSiteId() {
+ return mSiteId;
+ }
+
+ public long getPostId() {
+ return mPostId;
+ }
+
+ public void setPressed(boolean isPressed) {
+ this.mPressed = isPressed;
+ }
+
+ public String getUrl() {
+ return mUrl;
+ }
+
+ public boolean shouldShowBlogPreview() {
+ return mRangeType == NoteBlockRangeType.USER || mRangeType == NoteBlockRangeType.SITE;
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlockLinkMovementMethod.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlockLinkMovementMethod.java
new file mode 100644
index 000000000000..581b38c7368b
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlockLinkMovementMethod.java
@@ -0,0 +1,71 @@
+package org.wordpress.android.ui.notifications.blocks;
+
+import android.text.Layout;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.method.LinkMovementMethod;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Allows links to be highlighted when tapped on note blocks.
+ * See: http://stackoverflow.com/a/20905824/309558
+ */
+class NoteBlockLinkMovementMethod extends LinkMovementMethod {
+
+ private NoteBlockClickableSpan mPressedSpan;
+
+ @Override
+ public boolean onTouchEvent(@Nonnull TextView textView, @Nonnull Spannable spannable, @Nonnull MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mPressedSpan = getPressedSpan(textView, spannable, event);
+ if (mPressedSpan != null) {
+ mPressedSpan.setPressed(true);
+ Selection.setSelection(spannable, spannable.getSpanStart(mPressedSpan),
+ spannable.getSpanEnd(mPressedSpan));
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ NoteBlockClickableSpan touchedSpan = getPressedSpan(textView, spannable, event);
+ if (mPressedSpan != null && touchedSpan != mPressedSpan) {
+ mPressedSpan.setPressed(false);
+ mPressedSpan = null;
+ Selection.removeSelection(spannable);
+ }
+ } else {
+ if (mPressedSpan != null) {
+ mPressedSpan.setPressed(false);
+ super.onTouchEvent(textView, spannable, event);
+ }
+ mPressedSpan = null;
+ Selection.removeSelection(spannable);
+ }
+ return true;
+ }
+
+ private NoteBlockClickableSpan getPressedSpan(TextView textView, Spannable spannable, MotionEvent event) {
+
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ x -= textView.getTotalPaddingLeft();
+ y -= textView.getTotalPaddingTop();
+
+ x += textView.getScrollX();
+ y += textView.getScrollY();
+
+ Layout layout = textView.getLayout();
+ int line = layout.getLineForVertical(y);
+ int off = layout.getOffsetForHorizontal(line, x);
+
+ NoteBlockClickableSpan[] link = spannable.getSpans(off, off, NoteBlockClickableSpan.class);
+ NoteBlockClickableSpan touchedSpan = null;
+ if (link.length > 0) {
+ touchedSpan = link[0];
+ }
+
+ return touchedSpan;
+ }
+
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlockRangeType.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlockRangeType.java
new file mode 100644
index 000000000000..2eaf9958af2d
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/NoteBlockRangeType.java
@@ -0,0 +1,36 @@
+package org.wordpress.android.ui.notifications.blocks;
+
+import android.text.TextUtils;
+
+/**
+ * Known NoteBlock Range types
+ */
+public enum NoteBlockRangeType {
+ POST,
+ SITE,
+ COMMENT,
+ USER,
+ STAT,
+ BLOCKQUOTE,
+ UNKNOWN;
+
+ public static NoteBlockRangeType fromString(String value) {
+ if (TextUtils.isEmpty(value)) return UNKNOWN;
+
+ if (value.equals("post")) {
+ return POST;
+ } else if (value.equals("site")) {
+ return SITE;
+ } else if (value.equals("comment")) {
+ return COMMENT;
+ } else if (value.equals("user")) {
+ return USER;
+ } else if (value.equals("stat")) {
+ return STAT;
+ } else if (value.equals("blockquote")) {
+ return BLOCKQUOTE;
+ } else {
+ return UNKNOWN;
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java
new file mode 100644
index 000000000000..87b3b183a2cb
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java
@@ -0,0 +1,239 @@
+package org.wordpress.android.ui.notifications.blocks;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.TextView;
+
+import com.android.volley.toolbox.NetworkImageView;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.ui.notifications.NotificationsConstants;
+import org.wordpress.android.ui.notifications.utils.NotificationsUtils;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.JSONUtil;
+import org.wordpress.android.util.PhotonUtils;
+import org.wordpress.android.util.UrlUtils;
+
+/**
+ * A block that displays information about a User (such as a user that liked a post)
+ * Will display an action button if available (e.g. follow button)
+ */
+public class UserNoteBlock extends NoteBlock {
+ private final OnGravatarClickedListener mGravatarClickedListener;
+
+ private int mAvatarSz;
+
+ public interface OnGravatarClickedListener {
+ // userId is currently unused, but will be handy once a profile view is added to the app
+ public void onGravatarClicked(long siteId, long userId);
+ }
+
+ public UserNoteBlock(
+ Context context,
+ JSONObject noteObject,
+ OnNoteBlockTextClickListener onNoteBlockTextClickListener,
+ OnGravatarClickedListener onGravatarClickedListener) {
+ super(noteObject, onNoteBlockTextClickListener);
+ if (context != null) {
+ setAvatarSize(context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_large));
+ }
+ mGravatarClickedListener = onGravatarClickedListener;
+ }
+
+ void setAvatarSize(int size) {
+ mAvatarSz = size;
+ }
+
+ int getAvatarSize() {
+ return mAvatarSz;
+ }
+
+ @Override
+ public BlockType getBlockType() {
+ return BlockType.USER;
+ }
+
+ @Override
+ public int getLayoutResourceId() {
+ return R.layout.note_block_user;
+ }
+
+ @Override
+ public View configureView(View view) {
+ final UserActionNoteBlockHolder noteBlockHolder = (UserActionNoteBlockHolder)view.getTag();
+ noteBlockHolder.nameTextView.setText(getNoteText().toString());
+
+
+ String linkedText = null;
+ if (hasUserUrlAndTitle()) {
+ linkedText = getUserBlogTitle();
+ } else if (hasUserUrl()) {
+ linkedText = getUserUrl();
+ }
+
+ if (!TextUtils.isEmpty(linkedText)) {
+ noteBlockHolder.urlTextView.setText(NotificationsUtils.getClickableTextForIdUrl(
+ getUrlRangeObject(),
+ linkedText,
+ getOnNoteBlockTextClickListener()
+ ));
+ noteBlockHolder.urlTextView.setVisibility(View.VISIBLE);
+ } else {
+ noteBlockHolder.urlTextView.setVisibility(View.GONE);
+ }
+
+ if (hasUserBlogTagline()) {
+ noteBlockHolder.taglineTextView.setText(getUserBlogTagline());
+ noteBlockHolder.taglineTextView.setVisibility(View.VISIBLE);
+ } else if (hasUserUrlAndTitle()) {
+ noteBlockHolder.taglineTextView.setText(getUserUrl());
+ noteBlockHolder.taglineTextView.setVisibility(View.VISIBLE);
+ } else {
+ noteBlockHolder.taglineTextView.setVisibility(View.GONE);
+ }
+
+ if (hasImageMediaItem()) {
+ String imageUrl = PhotonUtils.fixAvatar(getNoteMediaItem().optString("url", ""), getAvatarSize());
+ noteBlockHolder.avatarImageView.setImageUrl(imageUrl, WordPress.imageLoader);
+ if (!TextUtils.isEmpty(getUserUrl())) {
+ noteBlockHolder.avatarImageView.setOnTouchListener(mOnGravatarTouchListener);
+ noteBlockHolder.rootView.setBackgroundResource(R.drawable.notifications_header_selector);
+ noteBlockHolder.rootView.setOnClickListener(mOnClickListener);
+ } else {
+ noteBlockHolder.avatarImageView.setOnTouchListener(null);
+ noteBlockHolder.rootView.setBackgroundColor(NotificationsConstants.COLOR_CALYPSO_WHITE);
+ noteBlockHolder.rootView.setOnClickListener(null);
+ }
+ } else {
+ noteBlockHolder.avatarImageView.setImageResource(R.drawable.placeholder);
+ noteBlockHolder.avatarImageView.setOnTouchListener(null);
+ }
+
+ return view;
+ }
+
+ private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showBlogPreview();
+ }
+ };
+
+ @Override
+ public Object getViewHolder(View view) {
+ return new UserActionNoteBlockHolder(view);
+ }
+
+ private class UserActionNoteBlockHolder {
+ private final View rootView;
+ private final TextView nameTextView;
+ private final TextView urlTextView;
+ private final TextView taglineTextView;
+ private final NetworkImageView avatarImageView;
+
+ public UserActionNoteBlockHolder(View view) {
+ rootView = view.findViewById(R.id.user_block_root_view);
+ nameTextView = (TextView)view.findViewById(R.id.user_name);
+ urlTextView = (TextView)view.findViewById(R.id.user_blog_url);
+ urlTextView.setMovementMethod(new NoteBlockLinkMovementMethod());
+ taglineTextView = (TextView)view.findViewById(R.id.user_blog_tagline);
+ avatarImageView = (NetworkImageView)view.findViewById(R.id.user_avatar);
+ }
+ }
+
+ JSONObject getUrlRangeObject() {
+ if (getNoteData() == null) return null;
+
+ JSONArray rangesArray = getNoteData().optJSONArray("ranges");
+ if (rangesArray != null) {
+ for (int i=0; i < rangesArray.length(); i++) {
+ try {
+ JSONObject rangeObject = rangesArray.getJSONObject(i);
+ if (rangeObject.has("url")) {
+ return rangeObject;
+ }
+ } catch (JSONException e) {
+ AppLog.i(AppLog.T.NOTIFS, "Unexpected object in notifications ids array.");
+ }
+ }
+ }
+
+ return null;
+ }
+
+ String getUserUrl() {
+ if (getUrlRangeObject() != null) {
+ String url = UrlUtils.normalizeUrl(getUrlRangeObject().optString("url", ""));
+ return url.replace("http://", "").replace("https://", "");
+ }
+
+ return null;
+ }
+
+ private String getUserBlogTitle() {
+ return JSONUtil.queryJSON(getNoteData(), "meta.titles.home", "");
+ }
+
+ private String getUserBlogTagline() {
+ return JSONUtil.queryJSON(getNoteData(), "meta.titles.tagline", "");
+ }
+
+ private boolean hasUserUrl() {
+ return !TextUtils.isEmpty(getUserUrl());
+ }
+
+ private boolean hasUserUrlAndTitle() {
+ return hasUserUrl() && !TextUtils.isEmpty(getUserBlogTitle());
+ }
+
+ private boolean hasUserBlogTagline() {
+ return !TextUtils.isEmpty(getUserBlogTagline());
+ }
+
+ final View.OnTouchListener mOnGravatarTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+
+ int animationDuration = 150;
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ v.animate()
+ .scaleX(0.9f)
+ .scaleY(0.9f)
+ .alpha(0.5f)
+ .setDuration(animationDuration)
+ .setInterpolator(new DecelerateInterpolator());
+ } else if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+ v.animate()
+ .scaleX(1.0f)
+ .scaleY(1.0f)
+ .alpha(1.0f)
+ .setDuration(animationDuration)
+ .setInterpolator(new DecelerateInterpolator());
+
+ if (event.getActionMasked() == MotionEvent.ACTION_UP && mGravatarClickedListener != null) {
+ // Fire the listener, which will load the site preview for the user's site
+ // In the future we can use this to load a 'profile view' (currently in R&D)
+ showBlogPreview();
+ }
+ }
+
+ return true;
+ }
+ };
+
+ private void showBlogPreview() {
+ long siteId = Long.valueOf(JSONUtil.queryJSON(getNoteData(), "meta.ids.site", 0));
+ long userId = Long.valueOf(JSONUtil.queryJSON(getNoteData(), "meta.ids.user", 0));
+ if (mGravatarClickedListener != null && siteId > 0 && userId > 0) {
+ mGravatarClickedListener.onGravatarClicked(siteId, userId);
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtils.java
similarity index 59%
rename from WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationUtils.java
rename to WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtils.java
index ee45e3a4161a..2fe64ec4ae26 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationUtils.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtils.java
@@ -1,10 +1,16 @@
-package org.wordpress.android.ui.notifications;
+package org.wordpress.android.ui.notifications.utils;
import android.content.Context;
import android.content.SharedPreferences;
+import android.graphics.Typeface;
import android.os.Build;
import android.preference.PreferenceManager;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.view.View;
import com.android.volley.VolleyError;
import com.google.android.gcm.GCMRegistrar;
@@ -12,10 +18,15 @@
import com.google.gson.internal.StringMap;
import com.wordpress.rest.RestRequest;
+import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.wordpress.android.BuildConfig;
import org.wordpress.android.WordPress;
+import org.wordpress.android.ui.notifications.NotificationsActivity;
+import org.wordpress.android.ui.notifications.blocks.NoteBlock;
+import org.wordpress.android.ui.notifications.blocks.NoteBlockClickableSpan;
+import org.wordpress.android.ui.notifications.blocks.NoteBlockRangeType;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.android.util.DeviceUtils;
@@ -25,7 +36,7 @@
import java.util.HashMap;
import java.util.Map;
-public class NotificationUtils {
+public class NotificationsUtils {
public static final String WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS = "wp_pref_notification_settings";
private static final String WPCOM_PUSH_DEVICE_SERVER_ID = "wp_pref_notifications_server_id";
@@ -112,7 +123,7 @@ public static void setPushNotificationSettings(Context context) {
Map contentStruct = new HashMap();
contentStruct.put("device_token", gcmToken);
contentStruct.put("device_family", "android");
- contentStruct.put("app_secret_key", NotificationUtils.getAppPushNotificationsName());
+ contentStruct.put("app_secret_key", NotificationsUtils.getAppPushNotificationsName());
contentStruct.put("settings", gson.toJson(updatedSettings));
WordPress.getRestClientUtils().post("/device/"+deviceID, contentStruct, null, null, null);
}
@@ -127,13 +138,13 @@ public static void registerDeviceForPushNotifications(final Context ctx, String
Map contentStruct = new HashMap();
contentStruct.put("device_token", token);
contentStruct.put("device_family", "android");
- contentStruct.put("app_secret_key", NotificationUtils.getAppPushNotificationsName());
+ contentStruct.put("app_secret_key", NotificationsUtils.getAppPushNotificationsName());
contentStruct.put("device_name", deviceName);
contentStruct.put("device_model", Build.MANUFACTURER + " " + Build.MODEL);
contentStruct.put("app_version", WordPress.versionName);
- contentStruct.put("os_version", android.os.Build.VERSION.RELEASE);
+ contentStruct.put("os_version", Build.VERSION.RELEASE);
contentStruct.put("device_uuid", uuid);
- com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ RestRequest.Listener listener = new RestRequest.Listener() {
@Override
public void onResponse(JSONObject jsonObject) {
AppLog.d(T.NOTIFS, "Register token action succeeded");
@@ -148,7 +159,7 @@ public void onResponse(JSONObject jsonObject) {
editor.putString(WPCOM_PUSH_DEVICE_SERVER_ID, deviceID);
JSONObject settingsJSON = jsonObject.getJSONObject("settings");
editor.putString(WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS, settingsJSON.toString());
- editor.commit();
+ editor.apply();
AppLog.d(T.NOTIFS, "Server response OK. The device_id : " + deviceID);
} catch (JSONException e1) {
AppLog.e(T.NOTIFS, "Server response is NOT ok. Registration skipped!!", e1);
@@ -166,7 +177,7 @@ public void onErrorResponse(VolleyError volleyError) {
}
public static void unregisterDevicePushNotifications(final Context ctx) {
- com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ RestRequest.Listener listener = new RestRequest.Listener() {
@Override
public void onResponse(JSONObject jsonObject) {
AppLog.d(T.NOTIFS, "Unregister token action succeeded");
@@ -174,7 +185,7 @@ public void onResponse(JSONObject jsonObject) {
editor.remove(WPCOM_PUSH_DEVICE_SERVER_ID);
editor.remove(WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS);
editor.remove(WPCOM_PUSH_DEVICE_UUID);
- editor.commit();
+ editor.apply();
}
};
RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
@@ -192,7 +203,7 @@ public void onErrorResponse(VolleyError volleyError) {
WordPress.getRestClientUtils().post("/devices/" + deviceID + "/delete", listener, errorListener);
}
- public static String getAppPushNotificationsName(){
+ private static String getAppPushNotificationsName() {
//white listing only few keys.
if (BuildConfig.APP_PN_KEY.equals("org.wordpress.android.beta.build"))
return "org.wordpress.android.beta.build";
@@ -201,4 +212,99 @@ public static String getAppPushNotificationsName(){
return "org.wordpress.android.playstore";
}
+
+ public static Spannable getSpannableTextFromIndices(JSONObject subject,
+ final NoteBlock.OnNoteBlockTextClickListener onNoteBlockTextClickListener) {
+ if (subject == null) {
+ return new SpannableStringBuilder();
+ }
+
+ String text = subject.optString("text", "").trim();
+ SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);
+
+ boolean shouldLink = onNoteBlockTextClickListener != null;
+
+ try {
+ JSONArray rangesArray = subject.getJSONArray("ranges");
+
+ for (int i=0; i < rangesArray.length(); i++) {
+ JSONObject rangeObject = (JSONObject) rangesArray.get(i);
+ NoteBlockClickableSpan clickableSpan = new NoteBlockClickableSpan(rangeObject, shouldLink) {
+ @Override
+ public void onClick(View widget) {
+ if (onNoteBlockTextClickListener != null) {
+ onNoteBlockTextClickListener.onNoteBlockTextClicked(this);
+ }
+ }
+ };
+ int[] indices = clickableSpan.getIndices();
+ if (indices.length == 2 && indices[0] <= spannableStringBuilder.length() &&
+ indices[1] <= spannableStringBuilder.length()) {
+ spannableStringBuilder.setSpan(clickableSpan, indices[0], indices[1], Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+ // Add additional styling if the id wants it
+ if (clickableSpan.getSpanStyle() != Typeface.NORMAL) {
+ StyleSpan styleSpan = new StyleSpan(clickableSpan.getSpanStyle());
+ spannableStringBuilder.setSpan(styleSpan, indices[0], indices[1], Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ }
+ }
+ } catch (JSONException e) {
+ return spannableStringBuilder;
+ }
+
+ return spannableStringBuilder;
+ }
+
+ public static Spannable getClickableTextForIdUrl(JSONObject idBlock, String text,
+ final NoteBlock.OnNoteBlockTextClickListener onNoteBlockTextClickListener) {
+ if (idBlock == null || TextUtils.isEmpty(text)) {
+ return new SpannableStringBuilder("");
+ }
+
+ boolean shouldLink = onNoteBlockTextClickListener != null;
+
+ NoteBlockClickableSpan clickableSpan = new NoteBlockClickableSpan(idBlock, shouldLink) {
+ @Override
+ public void onClick(View widget) {
+ if (onNoteBlockTextClickListener != null) {
+ onNoteBlockTextClickListener.onNoteBlockTextClicked(this);
+ }
+ }
+ };
+
+ SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);
+ spannableStringBuilder.setSpan(clickableSpan, 0, spannableStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+ return spannableStringBuilder;
+ }
+
+ public static void handleNoteBlockSpanClick(NotificationsActivity activity, NoteBlockClickableSpan clickedSpan) {
+ if (clickedSpan.shouldShowBlogPreview()) {
+ // Show blog preview
+ activity.showBlogPreviewActivity(clickedSpan.getSiteId(), clickedSpan.getUrl());
+ } else if (clickedSpan.getRangeType() == NoteBlockRangeType.POST) {
+ // Show post detail
+ activity.showPostActivity(clickedSpan.getSiteId(), clickedSpan.getId());
+ } else if (clickedSpan.getRangeType() == NoteBlockRangeType.COMMENT) {
+ // For now, show post detail for comments
+ activity.showPostActivity(clickedSpan.getSiteId(), clickedSpan.getPostId());
+ } else if (clickedSpan.getRangeType() == NoteBlockRangeType.STAT) {
+ // We can open native stats, but only if the site is stored in the app locally.
+ int localTableSiteId = WordPress.wpDB.getLocalTableBlogIdForRemoteBlogId(
+ (int)clickedSpan.getSiteId()
+ );
+
+ if (localTableSiteId > 0) {
+ activity.showStatsActivityForSite(localTableSiteId);
+ } else if (!TextUtils.isEmpty(clickedSpan.getUrl())) {
+ activity.showWebViewActivityForUrl(clickedSpan.getUrl());
+ }
+ } else {
+ // We don't know what type of id this is, let's see if it has a URL and push a webview
+ if (!TextUtils.isEmpty(clickedSpan.getUrl())) {
+ activity.showWebViewActivityForUrl(clickedSpan.getUrl());
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/SimperiumUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/SimperiumUtils.java
similarity index 95%
rename from WordPress/src/main/java/org/wordpress/android/ui/notifications/SimperiumUtils.java
rename to WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/SimperiumUtils.java
index 5fcefd283705..18b590475127 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/SimperiumUtils.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/SimperiumUtils.java
@@ -2,7 +2,7 @@
* Simperium integration with WordPress.com
* Currently used with Notifications
*/
-package org.wordpress.android.ui.notifications;
+package org.wordpress.android.ui.notifications.utils;
import android.content.Context;
import android.content.Intent;
@@ -26,10 +26,6 @@ public class SimperiumUtils {
private static Bucket mNotesBucket;
private static Bucket mMetaBucket;
- public static Simperium getSimperium() {
- return mSimperium;
- }
-
public static Bucket getNotesBucket() {
return mNotesBucket;
}
@@ -82,7 +78,7 @@ public void onUserStatusChange(User.Status status) {
return mSimperium;
}
- public static void authorizeUser(Simperium simperium, String token) {
+ private static void authorizeUser(Simperium simperium, String token) {
User user = simperium.getUser();
String tokenFormat = "WPCC/%s/%s";
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/PreferencesActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/PreferencesActivity.java
index de9aaf67a2fd..884a27ed1ed7 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/PreferencesActivity.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/PreferencesActivity.java
@@ -45,7 +45,7 @@
import org.wordpress.android.ui.accounts.ManageBlogsActivity;
import org.wordpress.android.ui.accounts.NewBlogActivity;
import org.wordpress.android.ui.accounts.WelcomeActivity;
-import org.wordpress.android.ui.notifications.NotificationUtils;
+import org.wordpress.android.ui.notifications.utils.NotificationsUtils;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.android.util.MapUtils;
@@ -124,7 +124,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
// AuthenticatorRequest notification settings if needed
if (WordPress.hasValidWPComCredentials(PreferencesActivity.this)) {
- String settingsJson = mSettings.getString(NotificationUtils.WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS, null);
+ String settingsJson = mSettings.getString(NotificationsUtils.WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS, null);
if (settingsJson == null) {
com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
@Override
@@ -133,7 +133,7 @@ public void onResponse(JSONObject jsonObject) {
Editor editor = mSettings.edit();
try {
JSONObject settingsJSON = jsonObject.getJSONObject("settings");
- editor.putString(NotificationUtils.WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS, settingsJSON.toString());
+ editor.putString(NotificationsUtils.WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS, settingsJSON.toString());
editor.commit();
} catch (JSONException e) {
AppLog.e(T.NOTIFS, "Can't parse the JSON object returned from the server that contains PN settings.", e);
@@ -146,7 +146,7 @@ public void onResponse(JSONObject jsonObject) {
public void onErrorResponse(VolleyError volleyError) {
AppLog.e(T.NOTIFS, "Get settings action failed", volleyError); }
};
- NotificationUtils.getPushNotificationSettings(PreferencesActivity.this, listener, errorListener);
+ NotificationsUtils.getPushNotificationSettings(PreferencesActivity.this, listener, errorListener);
}
}
@@ -492,9 +492,9 @@ protected Void doInBackground(Void... params) {
SharedPreferences.Editor editor = settings.edit();
Gson gson = new Gson();
String settingsJson = gson.toJson(mNotificationSettings);
- editor.putString(NotificationUtils.WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS, settingsJson);
+ editor.putString(NotificationsUtils.WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS, settingsJson);
editor.commit();
- NotificationUtils.setPushNotificationSettings(PreferencesActivity.this);
+ NotificationsUtils.setPushNotificationSettings(PreferencesActivity.this);
}
return null;
}
@@ -611,7 +611,7 @@ private void loadNotifications() {
notificationTypesCategory.removeAll();
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
- String settingsJson = settings.getString(NotificationUtils.WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS, null);
+ String settingsJson = settings.getString(NotificationsUtils.WPCOM_PUSH_DEVICE_NOTIFICATION_SETTINGS, null);
if (settingsJson == null) {
rootScreen.removePreference(mNotificationsGroup);
return;
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java
index 832605d939c3..b8659a6df5a7 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java
@@ -112,7 +112,7 @@ static ReaderPostListFragment newInstance(ReaderTag tag, ReaderPostListType list
/*
* show posts in a specific blog
*/
- static ReaderPostListFragment newInstance(long blogId, String blogUrl) {
+ public static ReaderPostListFragment newInstance(long blogId, String blogUrl) {
AppLog.d(T.READER, "reader post list > newInstance (blog)");
Bundle args = new Bundle();
diff --git a/WordPress/src/main/java/org/wordpress/android/widgets/NoticonTextView.java b/WordPress/src/main/java/org/wordpress/android/widgets/NoticonTextView.java
new file mode 100644
index 000000000000..7068264cffcb
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/widgets/NoticonTextView.java
@@ -0,0 +1,28 @@
+package org.wordpress.android.widgets;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/**
+ * TextView that uses noticon icon font
+ */
+public class NoticonTextView extends TextView {
+
+ private String FONT_NAME = "Noticons-Regular.otf";
+
+ public NoticonTextView(Context context) {
+ super(context, null);
+ this.setTypeface(TypefaceCache.getTypefaceForTypefaceName(context, FONT_NAME));
+ }
+
+ public NoticonTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ this.setTypeface(TypefaceCache.getTypefaceForTypefaceName(context, FONT_NAME));
+ }
+
+ public NoticonTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ this.setTypeface(TypefaceCache.getTypefaceForTypefaceName(context, FONT_NAME));
+ }
+}
\ No newline at end of file
diff --git a/WordPress/src/main/java/org/wordpress/android/widgets/OpenSansButton.java b/WordPress/src/main/java/org/wordpress/android/widgets/OpenSansButton.java
index 9fa6dd26f748..6324d22b0b4f 100644
--- a/WordPress/src/main/java/org/wordpress/android/widgets/OpenSansButton.java
+++ b/WordPress/src/main/java/org/wordpress/android/widgets/OpenSansButton.java
@@ -6,7 +6,7 @@
public class OpenSansButton extends Button {
public OpenSansButton(Context context) {
- super(context);
+ super(context, null);
TypefaceCache.setCustomTypeface(context, this, null);
}
diff --git a/WordPress/src/main/java/org/wordpress/android/widgets/OpenSansEditText.java b/WordPress/src/main/java/org/wordpress/android/widgets/OpenSansEditText.java
index 42d913836036..a5a9a9a5a9e2 100644
--- a/WordPress/src/main/java/org/wordpress/android/widgets/OpenSansEditText.java
+++ b/WordPress/src/main/java/org/wordpress/android/widgets/OpenSansEditText.java
@@ -6,7 +6,7 @@
public class OpenSansEditText extends EditText {
public OpenSansEditText(Context context) {
- super(context);
+ super(context, null);
TypefaceCache.setCustomTypeface(context, this, null);
}
diff --git a/WordPress/src/main/java/org/wordpress/android/widgets/TypefaceCache.java b/WordPress/src/main/java/org/wordpress/android/widgets/TypefaceCache.java
index 7e6e3d731700..525a5c44220d 100644
--- a/WordPress/src/main/java/org/wordpress/android/widgets/TypefaceCache.java
+++ b/WordPress/src/main/java/org/wordpress/android/widgets/TypefaceCache.java
@@ -40,6 +40,10 @@ public static Typeface getTypeface(Context context, int fontStyle, int variation
break;
}
+ return getTypefaceForTypefaceName(context, typefaceName);
+ }
+
+ protected static Typeface getTypefaceForTypefaceName(Context context, String typefaceName) {
if (!mTypefaceCache.containsKey(typefaceName)) {
Typeface typeface = Typeface.createFromAsset(context.getApplicationContext().getAssets(), "fonts/"
+ typefaceName);
diff --git a/WordPress/src/main/java/org/wordpress/android/widgets/WPTextView.java b/WordPress/src/main/java/org/wordpress/android/widgets/WPTextView.java
index 852e67434090..540541aeb37d 100644
--- a/WordPress/src/main/java/org/wordpress/android/widgets/WPTextView.java
+++ b/WordPress/src/main/java/org/wordpress/android/widgets/WPTextView.java
@@ -10,7 +10,7 @@
*/
public class WPTextView extends TextView {
public WPTextView(Context context) {
- super(context);
+ super(context, null);
TypefaceCache.setCustomTypeface(context, this, null);
}
diff --git a/WordPress/src/main/res/anim/notifications_button_scale.xml b/WordPress/src/main/res/anim/notifications_button_scale.xml
new file mode 100644
index 000000000000..1e33859bfcf4
--- /dev/null
+++ b/WordPress/src/main/res/anim/notifications_button_scale.xml
@@ -0,0 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/anim/pop.xml b/WordPress/src/main/res/anim/pop.xml
new file mode 100644
index 000000000000..3da388a95560
--- /dev/null
+++ b/WordPress/src/main/res/anim/pop.xml
@@ -0,0 +1,11 @@
+
+
diff --git a/WordPress/src/main/res/color/moderate_button_text.xml b/WordPress/src/main/res/color/moderate_button_text.xml
index 9c4d5702a081..3a3d5ba8e854 100644
--- a/WordPress/src/main/res/color/moderate_button_text.xml
+++ b/WordPress/src/main/res/color/moderate_button_text.xml
@@ -5,7 +5,7 @@
-->
-
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_action_approve.png b/WordPress/src/main/res/drawable-hdpi/ic_action_approve.png
new file mode 100644
index 000000000000..d4728b55509e
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_action_approve.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_action_approve_active.png b/WordPress/src/main/res/drawable-hdpi/ic_action_approve_active.png
new file mode 100644
index 000000000000..933733d95844
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_action_approve_active.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_action_edit.png b/WordPress/src/main/res/drawable-hdpi/ic_action_edit.png
new file mode 100644
index 000000000000..bcde54e388c3
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_action_edit.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_action_like.png b/WordPress/src/main/res/drawable-hdpi/ic_action_like.png
new file mode 100644
index 000000000000..35510e1fd6d3
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_action_like.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_action_like_active.png b/WordPress/src/main/res/drawable-hdpi/ic_action_like_active.png
new file mode 100644
index 000000000000..9fda1cc76e84
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_action_like_active.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_action_more.png b/WordPress/src/main/res/drawable-hdpi/ic_action_more.png
new file mode 100644
index 000000000000..441fc8047c2b
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_action_more.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_action_spam.png b/WordPress/src/main/res/drawable-hdpi/ic_action_spam.png
new file mode 100644
index 000000000000..afc42f54b074
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_action_spam.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_action_trash.png b/WordPress/src/main/res/drawable-hdpi/ic_action_trash.png
new file mode 100644
index 000000000000..0be1e2e45f73
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_action_trash.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_cab_like.png b/WordPress/src/main/res/drawable-hdpi/ic_cab_like.png
new file mode 100644
index 000000000000..465509e4007a
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_cab_like.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/ic_cab_like_active.png b/WordPress/src/main/res/drawable-hdpi/ic_cab_like_active.png
new file mode 100644
index 000000000000..ed8b82f92d57
Binary files /dev/null and b/WordPress/src/main/res/drawable-hdpi/ic_cab_like_active.png differ
diff --git a/WordPress/src/main/res/drawable-hdpi/note_icon_achievement.png b/WordPress/src/main/res/drawable-hdpi/note_icon_achievement.png
deleted file mode 100644
index a16510da3055..000000000000
Binary files a/WordPress/src/main/res/drawable-hdpi/note_icon_achievement.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-hdpi/note_icon_automattcher.png b/WordPress/src/main/res/drawable-hdpi/note_icon_automattcher.png
deleted file mode 100644
index 02a347c3a2ba..000000000000
Binary files a/WordPress/src/main/res/drawable-hdpi/note_icon_automattcher.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-hdpi/note_icon_comment.png b/WordPress/src/main/res/drawable-hdpi/note_icon_comment.png
deleted file mode 100644
index 66e6f645fc8c..000000000000
Binary files a/WordPress/src/main/res/drawable-hdpi/note_icon_comment.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-hdpi/note_icon_like.png b/WordPress/src/main/res/drawable-hdpi/note_icon_like.png
deleted file mode 100644
index 7e172fe25784..000000000000
Binary files a/WordPress/src/main/res/drawable-hdpi/note_icon_like.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-hdpi/note_icon_reblog.png b/WordPress/src/main/res/drawable-hdpi/note_icon_reblog.png
deleted file mode 100644
index 8be5d1365980..000000000000
Binary files a/WordPress/src/main/res/drawable-hdpi/note_icon_reblog.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-hdpi/note_icon_spike.png b/WordPress/src/main/res/drawable-hdpi/note_icon_spike.png
deleted file mode 100644
index bffb31b6113d..000000000000
Binary files a/WordPress/src/main/res/drawable-hdpi/note_icon_spike.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-hdpi/note_icon_traffic_surge.png b/WordPress/src/main/res/drawable-hdpi/note_icon_traffic_surge.png
deleted file mode 100644
index 43b3b4f181b5..000000000000
Binary files a/WordPress/src/main/res/drawable-hdpi/note_icon_traffic_surge.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-hdpi/note_icon_trapper.png b/WordPress/src/main/res/drawable-hdpi/note_icon_trapper.png
deleted file mode 100644
index 35af380528fe..000000000000
Binary files a/WordPress/src/main/res/drawable-hdpi/note_icon_trapper.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_action_approve.png b/WordPress/src/main/res/drawable-mdpi/ic_action_approve.png
new file mode 100644
index 000000000000..7fd75ce88da7
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_action_approve.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_action_approve_active.png b/WordPress/src/main/res/drawable-mdpi/ic_action_approve_active.png
new file mode 100644
index 000000000000..1cbbbe403699
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_action_approve_active.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_action_edit.png b/WordPress/src/main/res/drawable-mdpi/ic_action_edit.png
new file mode 100644
index 000000000000..0dce5f794fa8
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_action_edit.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_action_like.png b/WordPress/src/main/res/drawable-mdpi/ic_action_like.png
new file mode 100644
index 000000000000..82730777a700
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_action_like.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_action_like_active.png b/WordPress/src/main/res/drawable-mdpi/ic_action_like_active.png
new file mode 100644
index 000000000000..072bfc0ab897
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_action_like_active.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_action_more.png b/WordPress/src/main/res/drawable-mdpi/ic_action_more.png
new file mode 100644
index 000000000000..6e5dc0473239
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_action_more.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_action_spam.png b/WordPress/src/main/res/drawable-mdpi/ic_action_spam.png
new file mode 100644
index 000000000000..5f45029cc717
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_action_spam.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_action_trash.png b/WordPress/src/main/res/drawable-mdpi/ic_action_trash.png
new file mode 100644
index 000000000000..7e66210ceb08
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_action_trash.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_cab_like.png b/WordPress/src/main/res/drawable-mdpi/ic_cab_like.png
new file mode 100644
index 000000000000..96e4917e9262
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_cab_like.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/ic_cab_like_active.png b/WordPress/src/main/res/drawable-mdpi/ic_cab_like_active.png
new file mode 100644
index 000000000000..02a6f1cfd5d7
Binary files /dev/null and b/WordPress/src/main/res/drawable-mdpi/ic_cab_like_active.png differ
diff --git a/WordPress/src/main/res/drawable-mdpi/note_icon_achievement.png b/WordPress/src/main/res/drawable-mdpi/note_icon_achievement.png
deleted file mode 100644
index f361a571f901..000000000000
Binary files a/WordPress/src/main/res/drawable-mdpi/note_icon_achievement.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-mdpi/note_icon_automattcher.png b/WordPress/src/main/res/drawable-mdpi/note_icon_automattcher.png
deleted file mode 100644
index f2476a142f74..000000000000
Binary files a/WordPress/src/main/res/drawable-mdpi/note_icon_automattcher.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-mdpi/note_icon_comment.png b/WordPress/src/main/res/drawable-mdpi/note_icon_comment.png
deleted file mode 100644
index d8cbe22c4f6d..000000000000
Binary files a/WordPress/src/main/res/drawable-mdpi/note_icon_comment.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-mdpi/note_icon_like.png b/WordPress/src/main/res/drawable-mdpi/note_icon_like.png
deleted file mode 100644
index 1cb98ea62818..000000000000
Binary files a/WordPress/src/main/res/drawable-mdpi/note_icon_like.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-mdpi/note_icon_reblog.png b/WordPress/src/main/res/drawable-mdpi/note_icon_reblog.png
deleted file mode 100644
index 486ba11113ec..000000000000
Binary files a/WordPress/src/main/res/drawable-mdpi/note_icon_reblog.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-mdpi/note_icon_spike.png b/WordPress/src/main/res/drawable-mdpi/note_icon_spike.png
deleted file mode 100644
index e6dc524d597a..000000000000
Binary files a/WordPress/src/main/res/drawable-mdpi/note_icon_spike.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-mdpi/note_icon_traffic_surge.png b/WordPress/src/main/res/drawable-mdpi/note_icon_traffic_surge.png
deleted file mode 100644
index 5eb5be35f226..000000000000
Binary files a/WordPress/src/main/res/drawable-mdpi/note_icon_traffic_surge.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-mdpi/note_icon_trapper.png b/WordPress/src/main/res/drawable-mdpi/note_icon_trapper.png
deleted file mode 100644
index 2692cbb2ddd4..000000000000
Binary files a/WordPress/src/main/res/drawable-mdpi/note_icon_trapper.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_action_approve.png b/WordPress/src/main/res/drawable-xhdpi/ic_action_approve.png
new file mode 100644
index 000000000000..9f6bbc148aa5
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_action_approve.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_action_approve_active.png b/WordPress/src/main/res/drawable-xhdpi/ic_action_approve_active.png
new file mode 100644
index 000000000000..f9734d13a34b
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_action_approve_active.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_action_edit.png b/WordPress/src/main/res/drawable-xhdpi/ic_action_edit.png
new file mode 100644
index 000000000000..d05d26e89939
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_action_edit.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_action_like.png b/WordPress/src/main/res/drawable-xhdpi/ic_action_like.png
new file mode 100644
index 000000000000..eb97cfce5056
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_action_like.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_action_like_active.png b/WordPress/src/main/res/drawable-xhdpi/ic_action_like_active.png
new file mode 100644
index 000000000000..ff452e2404d4
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_action_like_active.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_action_more.png b/WordPress/src/main/res/drawable-xhdpi/ic_action_more.png
new file mode 100644
index 000000000000..bc3076681f93
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_action_more.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_action_spam.png b/WordPress/src/main/res/drawable-xhdpi/ic_action_spam.png
new file mode 100644
index 000000000000..9d64a26b3356
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_action_spam.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_action_trash.png b/WordPress/src/main/res/drawable-xhdpi/ic_action_trash.png
new file mode 100644
index 000000000000..31325d129992
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_action_trash.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_cab_like.png b/WordPress/src/main/res/drawable-xhdpi/ic_cab_like.png
new file mode 100644
index 000000000000..cbf7d9ff81c1
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_cab_like.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_cab_like_active.png b/WordPress/src/main/res/drawable-xhdpi/ic_cab_like_active.png
new file mode 100644
index 000000000000..0c2060883b7e
Binary files /dev/null and b/WordPress/src/main/res/drawable-xhdpi/ic_cab_like_active.png differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/note_icon_achievement.png b/WordPress/src/main/res/drawable-xhdpi/note_icon_achievement.png
deleted file mode 100644
index 64620c8abbc1..000000000000
Binary files a/WordPress/src/main/res/drawable-xhdpi/note_icon_achievement.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/note_icon_automattcher.png b/WordPress/src/main/res/drawable-xhdpi/note_icon_automattcher.png
deleted file mode 100644
index 41ca319aa903..000000000000
Binary files a/WordPress/src/main/res/drawable-xhdpi/note_icon_automattcher.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/note_icon_comment.png b/WordPress/src/main/res/drawable-xhdpi/note_icon_comment.png
deleted file mode 100644
index 4b969cf155de..000000000000
Binary files a/WordPress/src/main/res/drawable-xhdpi/note_icon_comment.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/note_icon_like.png b/WordPress/src/main/res/drawable-xhdpi/note_icon_like.png
deleted file mode 100644
index 71da7d8f622c..000000000000
Binary files a/WordPress/src/main/res/drawable-xhdpi/note_icon_like.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/note_icon_reblog.png b/WordPress/src/main/res/drawable-xhdpi/note_icon_reblog.png
deleted file mode 100644
index 38586c656bc3..000000000000
Binary files a/WordPress/src/main/res/drawable-xhdpi/note_icon_reblog.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/note_icon_spike.png b/WordPress/src/main/res/drawable-xhdpi/note_icon_spike.png
deleted file mode 100644
index 86ec6c3e193d..000000000000
Binary files a/WordPress/src/main/res/drawable-xhdpi/note_icon_spike.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/note_icon_traffic_surge.png b/WordPress/src/main/res/drawable-xhdpi/note_icon_traffic_surge.png
deleted file mode 100644
index c23b5976c2a7..000000000000
Binary files a/WordPress/src/main/res/drawable-xhdpi/note_icon_traffic_surge.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xhdpi/note_icon_trapper.png b/WordPress/src/main/res/drawable-xhdpi/note_icon_trapper.png
deleted file mode 100644
index bde4471820d4..000000000000
Binary files a/WordPress/src/main/res/drawable-xhdpi/note_icon_trapper.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_action_approve.png b/WordPress/src/main/res/drawable-xxhdpi/ic_action_approve.png
new file mode 100644
index 000000000000..f718d0e20a28
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_action_approve.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_action_approve_active.png b/WordPress/src/main/res/drawable-xxhdpi/ic_action_approve_active.png
new file mode 100644
index 000000000000..d6bb54ab8da7
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_action_approve_active.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_action_edit.png b/WordPress/src/main/res/drawable-xxhdpi/ic_action_edit.png
new file mode 100644
index 000000000000..27e1082a7f53
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_action_edit.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_action_like.png b/WordPress/src/main/res/drawable-xxhdpi/ic_action_like.png
new file mode 100644
index 000000000000..318a5c324f84
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_action_like.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_action_like_active.png b/WordPress/src/main/res/drawable-xxhdpi/ic_action_like_active.png
new file mode 100644
index 000000000000..01110d83d9af
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_action_like_active.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_action_more.png b/WordPress/src/main/res/drawable-xxhdpi/ic_action_more.png
new file mode 100644
index 000000000000..1d9b539a9864
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_action_more.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_action_spam.png b/WordPress/src/main/res/drawable-xxhdpi/ic_action_spam.png
new file mode 100644
index 000000000000..6d4021d99af7
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_action_spam.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_action_trash.png b/WordPress/src/main/res/drawable-xxhdpi/ic_action_trash.png
new file mode 100644
index 000000000000..b1dc20c28c8f
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_action_trash.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_cab_like.png b/WordPress/src/main/res/drawable-xxhdpi/ic_cab_like.png
new file mode 100644
index 000000000000..54a95505cff8
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_cab_like.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_cab_like_active.png b/WordPress/src/main/res/drawable-xxhdpi/ic_cab_like_active.png
new file mode 100644
index 000000000000..dfabbf437633
Binary files /dev/null and b/WordPress/src/main/res/drawable-xxhdpi/ic_cab_like_active.png differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/note_icon_achievement.png b/WordPress/src/main/res/drawable-xxhdpi/note_icon_achievement.png
deleted file mode 100644
index 10821cbe2ad7..000000000000
Binary files a/WordPress/src/main/res/drawable-xxhdpi/note_icon_achievement.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/note_icon_automattcher.png b/WordPress/src/main/res/drawable-xxhdpi/note_icon_automattcher.png
deleted file mode 100644
index fd5e19bfe79f..000000000000
Binary files a/WordPress/src/main/res/drawable-xxhdpi/note_icon_automattcher.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/note_icon_comment.png b/WordPress/src/main/res/drawable-xxhdpi/note_icon_comment.png
deleted file mode 100644
index 9f4274b25f17..000000000000
Binary files a/WordPress/src/main/res/drawable-xxhdpi/note_icon_comment.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/note_icon_like.png b/WordPress/src/main/res/drawable-xxhdpi/note_icon_like.png
deleted file mode 100644
index c6793122d3ce..000000000000
Binary files a/WordPress/src/main/res/drawable-xxhdpi/note_icon_like.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/note_icon_reblog.png b/WordPress/src/main/res/drawable-xxhdpi/note_icon_reblog.png
deleted file mode 100644
index b4491bd27d10..000000000000
Binary files a/WordPress/src/main/res/drawable-xxhdpi/note_icon_reblog.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/note_icon_spike.png b/WordPress/src/main/res/drawable-xxhdpi/note_icon_spike.png
deleted file mode 100644
index e65efcd88085..000000000000
Binary files a/WordPress/src/main/res/drawable-xxhdpi/note_icon_spike.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/note_icon_traffic_surge.png b/WordPress/src/main/res/drawable-xxhdpi/note_icon_traffic_surge.png
deleted file mode 100644
index 837d60902473..000000000000
Binary files a/WordPress/src/main/res/drawable-xxhdpi/note_icon_traffic_surge.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable-xxhdpi/note_icon_trapper.png b/WordPress/src/main/res/drawable-xxhdpi/note_icon_trapper.png
deleted file mode 100644
index f1f88dc47138..000000000000
Binary files a/WordPress/src/main/res/drawable-xxhdpi/note_icon_trapper.png and /dev/null differ
diff --git a/WordPress/src/main/res/drawable/calypso_bordered_bg.xml b/WordPress/src/main/res/drawable/calypso_bordered_bg.xml
new file mode 100644
index 000000000000..bf5ad4c13a42
--- /dev/null
+++ b/WordPress/src/main/res/drawable/calypso_bordered_bg.xml
@@ -0,0 +1,14 @@
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/comment_reply_background.xml b/WordPress/src/main/res/drawable/comment_reply_background.xml
new file mode 100644
index 000000000000..0dec5a64dae7
--- /dev/null
+++ b/WordPress/src/main/res/drawable/comment_reply_background.xml
@@ -0,0 +1,24 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/comment_reply_unapproved_background.xml b/WordPress/src/main/res/drawable/comment_reply_unapproved_background.xml
new file mode 100644
index 000000000000..5f64569c76cf
--- /dev/null
+++ b/WordPress/src/main/res/drawable/comment_reply_unapproved_background.xml
@@ -0,0 +1,22 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/comment_unapproved_background.xml b/WordPress/src/main/res/drawable/comment_unapproved_background.xml
new file mode 100644
index 000000000000..b9fc7201b082
--- /dev/null
+++ b/WordPress/src/main/res/drawable/comment_unapproved_background.xml
@@ -0,0 +1,16 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/list_bg_selector.xml b/WordPress/src/main/res/drawable/list_bg_selector.xml
index 71293361c343..1d07bd47d572 100644
--- a/WordPress/src/main/res/drawable/list_bg_selector.xml
+++ b/WordPress/src/main/res/drawable/list_bg_selector.xml
@@ -4,15 +4,15 @@
+ android:drawable="@color/reader_list_selector" />
+ android:drawable="@color/reader_list_selector" />
+ android:drawable="@color/reader_list_selector" />
diff --git a/WordPress/src/main/res/drawable/list_unread_bg_selector.xml b/WordPress/src/main/res/drawable/list_unread_bg_selector.xml
new file mode 100644
index 000000000000..b51f61a69f85
--- /dev/null
+++ b/WordPress/src/main/res/drawable/list_unread_bg_selector.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/moderate_button_selector.xml b/WordPress/src/main/res/drawable/moderate_button_selector.xml
index de7a0b78e995..320b2b6d1f43 100644
--- a/WordPress/src/main/res/drawable/moderate_button_selector.xml
+++ b/WordPress/src/main/res/drawable/moderate_button_selector.xml
@@ -7,12 +7,12 @@
-
-
+
-
-
+
-
@@ -22,7 +22,7 @@
-
-
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/notifications_header_selector.xml b/WordPress/src/main/res/drawable/notifications_header_selector.xml
new file mode 100644
index 000000000000..1c29dd20e5b5
--- /dev/null
+++ b/WordPress/src/main/res/drawable/notifications_header_selector.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/notifications_list_divider.xml b/WordPress/src/main/res/drawable/notifications_list_divider.xml
new file mode 100644
index 000000000000..aec6e9da9500
--- /dev/null
+++ b/WordPress/src/main/res/drawable/notifications_list_divider.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/notifications_list_divider_full_width.xml b/WordPress/src/main/res/drawable/notifications_list_divider_full_width.xml
new file mode 100644
index 000000000000..9893ed2119c8
--- /dev/null
+++ b/WordPress/src/main/res/drawable/notifications_list_divider_full_width.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/reader_list_selector.xml b/WordPress/src/main/res/drawable/reader_list_selector.xml
index 5914df34a735..e4d514b3696a 100644
--- a/WordPress/src/main/res/drawable/reader_list_selector.xml
+++ b/WordPress/src/main/res/drawable/reader_list_selector.xml
@@ -6,6 +6,11 @@
+ -
+
+
+
+
-
diff --git a/WordPress/src/main/res/drawable/shape_oval_blue.xml b/WordPress/src/main/res/drawable/shape_oval_blue.xml
new file mode 100644
index 000000000000..3aeb98084a4a
--- /dev/null
+++ b/WordPress/src/main/res/drawable/shape_oval_blue.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/drawable/shape_oval_grey.xml b/WordPress/src/main/res/drawable/shape_oval_grey.xml
new file mode 100644
index 000000000000..15c9d1aedb5a
--- /dev/null
+++ b/WordPress/src/main/res/drawable/shape_oval_grey.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout-sw600dp-land/comment_activity.xml b/WordPress/src/main/res/layout-sw600dp-land/comment_activity.xml
deleted file mode 100644
index a3a429f2da8e..000000000000
--- a/WordPress/src/main/res/layout-sw600dp-land/comment_activity.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/WordPress/src/main/res/layout-sw600dp-land/notifications.xml b/WordPress/src/main/res/layout-sw600dp-land/notifications.xml
deleted file mode 100644
index 8a6dbcb872ff..000000000000
--- a/WordPress/src/main/res/layout-sw600dp-land/notifications.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout-sw720dp/comment_activity.xml b/WordPress/src/main/res/layout-sw720dp/comment_activity.xml
deleted file mode 100644
index a3a429f2da8e..000000000000
--- a/WordPress/src/main/res/layout-sw720dp/comment_activity.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/WordPress/src/main/res/layout-sw720dp/notifications.xml b/WordPress/src/main/res/layout-sw720dp/notifications.xml
deleted file mode 100644
index ffb557275d02..000000000000
--- a/WordPress/src/main/res/layout-sw720dp/notifications.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/comment_action_footer.xml b/WordPress/src/main/res/layout/comment_action_footer.xml
new file mode 100644
index 000000000000..3815b99a1bd8
--- /dev/null
+++ b/WordPress/src/main/res/layout/comment_action_footer.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/comment_activity.xml b/WordPress/src/main/res/layout/comment_activity.xml
index fded929a2d65..dfa641ed041d 100644
--- a/WordPress/src/main/res/layout/comment_activity.xml
+++ b/WordPress/src/main/res/layout/comment_activity.xml
@@ -1,7 +1,7 @@
diff --git a/WordPress/src/main/res/layout/comment_detail_fragment.xml b/WordPress/src/main/res/layout/comment_detail_fragment.xml
index 3fdac1bcb201..2f78a3d21307 100644
--- a/WordPress/src/main/res/layout/comment_detail_fragment.xml
+++ b/WordPress/src/main/res/layout/comment_detail_fragment.xml
@@ -8,82 +8,85 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/background_grey">
+ android:background="@color/calypso_blue_light">
+ android:layout_above="@+id/layout_bottom"
+ android:fillViewport="true">
+ android:orientation="vertical">
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/margin_extra_large"
+ android:paddingRight="@dimen/margin_extra_large"
+ android:paddingTop="@dimen/margin_large"
+ android:background="@color/white">
+ android:layout_marginTop="@dimen/margin_small" />
+ android:textSize="@dimen/text_sz_extra_large"
+ tools:text="text_name" />
+ android:textSize="@dimen/text_sz_large"
+ tools:text="text_post_title" />
@@ -95,15 +98,21 @@
+ android:paddingLeft="@dimen/margin_extra_large"
+ android:paddingRight="@dimen/margin_extra_large"
+ android:textColor="@color/grey_dark"
+ android:textColorLink="@color/reader_hyperlink"
+ android:background="@color/white"
+ android:textIsSelectable="false"
+ android:textSize="@dimen/text_sz_large"
+ tools:text="text_content" />
+
@@ -114,51 +123,6 @@
android:layout_alignParentBottom="true"
android:orientation="vertical">
-
-
-
-
-
-
-
-
-
-
-
-
+ android:paddingTop="@dimen/margin_large">
-
-
+ android:orientation="vertical">
-
+
-
+
+
+
+
+
+
+
+
+ android:indeterminate="true"
+ android:visibility="gone"/>
-
-
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/comment_list_fragment.xml b/WordPress/src/main/res/layout/comment_list_fragment.xml
index 04e71e6f4593..6a6c9c2f1297 100644
--- a/WordPress/src/main/res/layout/comment_list_fragment.xml
+++ b/WordPress/src/main/res/layout/comment_list_fragment.xml
@@ -20,7 +20,7 @@
android:dividerHeight="1dp"
android:listSelector="@drawable/reader_list_selector"
android:scrollingCache="true"
- android:textColor="#444444"
+ style="@style/WordPress.BorderedBackground"
tools:listitem="@layout/comment_listitem"/>
diff --git a/WordPress/src/main/res/layout/comment_listitem.xml b/WordPress/src/main/res/layout/comment_listitem.xml
index da6e43b6a7a7..f8c52728dbca 100644
--- a/WordPress/src/main/res/layout/comment_listitem.xml
+++ b/WordPress/src/main/res/layout/comment_listitem.xml
@@ -1,82 +1,104 @@
-
+ android:orientation="vertical">
-
+
-
+
-
-
+
-
+
-
+
+
+
+
-
-
+ android:gravity="right"
+ android:orientation="vertical">
-
+
-
+
+
-
\ No newline at end of file
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/empty_listview.xml b/WordPress/src/main/res/layout/empty_listview.xml
index 7dfa355b3fb5..68628a885e64 100644
--- a/WordPress/src/main/res/layout/empty_listview.xml
+++ b/WordPress/src/main/res/layout/empty_listview.xml
@@ -17,7 +17,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
- android:drawSelectorOnTop="false"/>
+ android:listSelector="@drawable/reader_list_selector" />
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/note_block_comment_user.xml b/WordPress/src/main/res/layout/note_block_comment_user.xml
new file mode 100644
index 000000000000..b87ceacda70b
--- /dev/null
+++ b/WordPress/src/main/res/layout/note_block_comment_user.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WordPress/src/main/res/layout/note_block_user.xml b/WordPress/src/main/res/layout/note_block_user.xml
new file mode 100644
index 000000000000..1348ff0dff49
--- /dev/null
+++ b/WordPress/src/main/res/layout/note_block_user.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/note_block_user_header.xml b/WordPress/src/main/res/layout/note_block_user_header.xml
new file mode 100644
index 000000000000..391863810ab5
--- /dev/null
+++ b/WordPress/src/main/res/layout/note_block_user_header.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/note_list_item.xml b/WordPress/src/main/res/layout/note_list_item.xml
deleted file mode 100644
index acf976a49523..000000000000
--- a/WordPress/src/main/res/layout/note_list_item.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications.xml b/WordPress/src/main/res/layout/notifications.xml
deleted file mode 100644
index 6fa84ce5f94f..000000000000
--- a/WordPress/src/main/res/layout/notifications.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications_activity.xml b/WordPress/src/main/res/layout/notifications_activity.xml
new file mode 100644
index 000000000000..26616d2bdff7
--- /dev/null
+++ b/WordPress/src/main/res/layout/notifications_activity.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications_big_badge.xml b/WordPress/src/main/res/layout/notifications_big_badge.xml
deleted file mode 100644
index eb7e3a3f6908..000000000000
--- a/WordPress/src/main/res/layout/notifications_big_badge.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications_detail_header.xml b/WordPress/src/main/res/layout/notifications_detail_header.xml
deleted file mode 100644
index 037f8f3a1324..000000000000
--- a/WordPress/src/main/res/layout/notifications_detail_header.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications_follow_list.xml b/WordPress/src/main/res/layout/notifications_follow_list.xml
deleted file mode 100644
index 40d87f866ba8..000000000000
--- a/WordPress/src/main/res/layout/notifications_follow_list.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications_follow_row.xml b/WordPress/src/main/res/layout/notifications_follow_row.xml
deleted file mode 100644
index b22f3fe588e7..000000000000
--- a/WordPress/src/main/res/layout/notifications_follow_row.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications_fragment_detail_list.xml b/WordPress/src/main/res/layout/notifications_fragment_detail_list.xml
new file mode 100644
index 000000000000..c65cbca126cf
--- /dev/null
+++ b/WordPress/src/main/res/layout/notifications_fragment_detail_list.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications_fragment_notes_list.xml b/WordPress/src/main/res/layout/notifications_fragment_notes_list.xml
new file mode 100644
index 000000000000..919af041d6b2
--- /dev/null
+++ b/WordPress/src/main/res/layout/notifications_fragment_notes_list.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications_list_item.xml b/WordPress/src/main/res/layout/notifications_list_item.xml
new file mode 100644
index 000000000000..89660ffdf834
--- /dev/null
+++ b/WordPress/src/main/res/layout/notifications_list_item.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/notifications_matcher.xml b/WordPress/src/main/res/layout/notifications_matcher.xml
deleted file mode 100644
index a9a0b6ba6ad6..000000000000
--- a/WordPress/src/main/res/layout/notifications_matcher.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/WordPress/src/main/res/layout/post_list_row.xml b/WordPress/src/main/res/layout/post_list_row.xml
index efae3753a9bf..3bcb25aa1c42 100644
--- a/WordPress/src/main/res/layout/post_list_row.xml
+++ b/WordPress/src/main/res/layout/post_list_row.xml
@@ -7,7 +7,7 @@
android:paddingRight="@dimen/margin_extra_large"
android:paddingTop="@dimen/margin_medium"
android:paddingBottom="@dimen/margin_medium"
- style="@style/WordPressListRowBackground">
+ android:background="@drawable/list_bg_selector">
+ android:drawSelectorOnTop="false"
+ android:listSelector="@drawable/reader_list_selector"/>
+ android:layout_height="1dp"
+ android:background="@color/calypso_blue_border" />
-
-
+ -6.5dp
+ 14.5dp
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/values-sw600dp-land/dimens.xml b/WordPress/src/main/res/values-sw600dp-land/dimens.xml
index 7dce9b7cc52c..8fac2f94462c 100644
--- a/WordPress/src/main/res/values-sw600dp-land/dimens.xml
+++ b/WordPress/src/main/res/values-sw600dp-land/dimens.xml
@@ -1,3 +1,4 @@
@dimen/reader_list_margin_landscape_tablet
+ 192dp
diff --git a/WordPress/src/main/res/values-sw600dp/dimens.xml b/WordPress/src/main/res/values-sw600dp/dimens.xml
index 3c9d94776280..b0aba5b1fc57 100644
--- a/WordPress/src/main/res/values-sw600dp/dimens.xml
+++ b/WordPress/src/main/res/values-sw600dp/dimens.xml
@@ -7,4 +7,6 @@
@dimen/reader_list_margin_tablet
@dimen/reader_featured_image_height_tablet
+
+ 48dp
diff --git a/WordPress/src/main/res/values-sw600dp/styles.xml b/WordPress/src/main/res/values-sw600dp/styles.xml
new file mode 100644
index 000000000000..7061ed70a366
--- /dev/null
+++ b/WordPress/src/main/res/values-sw600dp/styles.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/values-xhdpi/dimens.xml b/WordPress/src/main/res/values-xhdpi/dimens.xml
new file mode 100644
index 000000000000..c37a22414416
--- /dev/null
+++ b/WordPress/src/main/res/values-xhdpi/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ -6dp
+ 15dp
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/values/colors.xml b/WordPress/src/main/res/values/colors.xml
index 1fcef9c9090b..385d6291de88 100644
--- a/WordPress/src/main/res/values/colors.xml
+++ b/WordPress/src/main/res/values/colors.xml
@@ -105,4 +105,16 @@
#646464
+
+
+
+ #00aadc
+ #324155
+ #aac0cf
+ #90aec2
+ #d2dee6
+ #e8f0f5
+ #fff8e5
+ #ffba00
+ #f0821e
diff --git a/WordPress/src/main/res/values/dimens.xml b/WordPress/src/main/res/values/dimens.xml
index 7fee4b237e47..b5f203742350 100644
--- a/WordPress/src/main/res/values/dimens.xml
+++ b/WordPress/src/main/res/values/dimens.xml
@@ -56,8 +56,8 @@
16sp
20sp
- 48dp
32dp
+ 48dp
64dp
48dp
@@ -95,8 +95,11 @@
600dp
- 20dp
- 48dp
+ 21dp
+ 6dp
+ 10dp
+ -6dp
+ 15dp
3dp
8dp
diff --git a/WordPress/src/main/res/values/ids.xml b/WordPress/src/main/res/values/ids.xml
index dc3d67526b88..fead989ec40d 100644
--- a/WordPress/src/main/res/values/ids.xml
+++ b/WordPress/src/main/res/values/ids.xml
@@ -11,4 +11,5 @@
+
\ No newline at end of file
diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml
index a2fd91992a87..6c2a1cd57213 100644
--- a/WordPress/src/main/res/values/strings.xml
+++ b/WordPress/src/main/res/values/strings.xml
@@ -115,7 +115,7 @@
Enter a description here
Updated
Failed to update
- Saving…
+ Saving…
Are you sure you want to delete this media item?
@@ -123,7 +123,7 @@
Wait until upload completes
Some media can\'t be deleted at this time. Try again later.
No media
- No media in this time interval
+ No media in this time interval
Delete
@@ -152,14 +152,14 @@
Title
- No pages yet. Why not create one?
+ No pages yet. Why not create one?
Page
Page settings
Untitled
Local draft
- No posts yet. Why not create one?
+ No posts yet. Why not create one?
This list is empty
@@ -179,11 +179,11 @@
Delete post
Delete page
Share URL
- Loading posts…
- Loading pages…
+ Loading posts…
+ Loading pages…
- Loading…
+ Loading…
on
@@ -193,6 +193,12 @@
Trashed
Edit comment
No comments
+ Reply to %s
+ Mark this comment as spam?
+ Mark spam
+ Comment trashed
+ Comment marked as spam
+ Comment
Approve
@@ -201,6 +207,7 @@
Not spam
Trash
Edit
+ Liked
Approving
@@ -266,6 +273,7 @@
+
Select a photo from gallery
Select a video from gallery
@@ -286,7 +294,7 @@
Select a blog
- Add to …
+ Add to …
New post
Media library
Share
@@ -384,8 +392,7 @@
Content
Followers
- includes %sPublicize
+ includes %sPublicize
Shares
Posts
Categories
@@ -473,6 +480,10 @@
Follow or unfollow this blog
Reply failed
No notifications
+ Older than 2 days
+ Older than a week
+ Older than a month
+ More
Reader
@@ -491,7 +502,7 @@
- Video
-
+
New post
New page
New media
@@ -503,6 +514,7 @@
Alignment
+
- None
- Left
@@ -528,7 +540,7 @@
WordPress blog
blogusername
-
+
An error occurred while deleting the %s
Posts couldn\'t be refreshed at this time
@@ -550,7 +562,7 @@
This blog is hidden and couldn\'t be loaded. Enable it again in settings and try again.
An error occurred while creating the app database. Try reinstalling the app.
-
+
Add media
Couldn\'t load the comment
Manage
@@ -579,6 +591,7 @@
***
-->
+
now
@@ -605,7 +618,7 @@
Shared from %s
-
+
Tags
Block this blog
@@ -673,14 +686,14 @@
Unable to perform this action
- No posts with this tag
+ No posts with this tag
Fetching posts…
- You don\'t follow any tags
+ You don\'t follow any tags
No popular tags
No recommended blogs
- You\'re not following any blogs yet
- But don\'t worry, just tap the tag icon to start exploring!
- You haven\'t liked any posts
+ You\'re not following any blogs yet
+ But don\'t worry, just tap the tag icon to start exploring!
+ You haven\'t liked any posts
fragment_comment_list
@@ -691,8 +704,8 @@
dual_pane_mode
- Create an account on WordPress.com
- Create WordPress.com blog
+ Create an account on WordPress.com
+ Create WordPress.com blog
Validating user data
Validating site data
Creating your account
@@ -703,7 +716,7 @@
Username must be longer than 4 characters
Username must be shorter than 61 characters
Email address
- By creating an account you agree to the fascinating %1$sTerms of Service%2$s
+ By creating an account you agree to the fascinating %1$sTerms of Service%2$s
Username or email
Your self-hosted address (URL)
Connecting to WordPress.com
@@ -712,7 +725,7 @@
Username not allowed
You can\'t use that email address to signup. We are having problems with them blocking some of our email. Use another email provider.
Username must be at least 4 characters
- Username may not contain the character “_”
+ Username may not contain the character “_”
Username must have a least 1 letter (a-z)
Enter a valid email address
That email address isn\'t allowed
@@ -724,7 +737,7 @@
That site address isn\'t allowed
Site address must be at least 4 characters
The site address must be shorter than 64 characters
- Site address may not contain the character “_”
+ Site address may not contain the character “_”
You may not use that site address
Site address can only contain lowercase letters (a-z) and numbers
Site address must have at least 1 letter (a-z)
@@ -755,7 +768,7 @@
Help
Lost your password?
- Visit the help center to get answers to common questions or visit the forums to ask new ones
+ Visit the help center to get answers to common questions or visit the forums to ask new ones
Forums
Contact us
Get help from Automattic support
diff --git a/WordPress/src/main/res/values/styles.xml b/WordPress/src/main/res/values/styles.xml
index 52b9f9d61591..bf8b325e4a41 100644
--- a/WordPress/src/main/res/values/styles.xml
+++ b/WordPress/src/main/res/values/styles.xml
@@ -1,6 +1,5 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WordPress/src/res/strings.xml b/WordPress/src/res/strings.xml
deleted file mode 100644
index b7651ab0e9e0..000000000000
--- a/WordPress/src/res/strings.xml
+++ /dev/null
@@ -1,569 +0,0 @@
-
-
- Adicionar localização
- Localização atual
- Sua assinatura:
- Pesquisa
-
- Localizar
- Privacidade
- Enviar estatísticas
- Envie estatísticas de uso automaticamente para nos ajudar a melhorar o WordPress para Android
- Você chegou ao fim
- Toque para fechar
- Reblogar um Post
- Adicionar sua opinião (opcional)
- Reblogar para
- Atualizar
- Agendar
- Entrar novamente
- Não foi possível entrar para obter notificações.
- A url XMLRPC retornada pela instância WordPress é inválida. Contate o administrador do site.
- Visualizar Blog
- Visualizar Tag
- Tags e Blogs
- Tags seguidas
- Tags populares
- Blogs seguidos
- Blogs recomendados
- Digite uma tag ou URL para seguir
- Mostrar novos posts
- %s seguidores
- Seguindo o blog
- Deixou de seguir o blog
- Mais recomendações
- Posts com tag %s
- Não é possível mostrar o blog
- Você já segue o blog
- Não é possível seguir o blog
- Não é possível deixar de seguir o blog
- Nenhum blog recomendado
- Blogs
- Nenhuma página ou post principal foi encontrado.
- Essa exibição mostra seus posts e suas páginas mais visualizadas.
- Sem referer.
- Um referer é um clique de outro site que direciona para o seu.
- Nenhum clique registrado.
- "Cliques" são visualizadores que clicam em links externos do seu site.
- Nenhum termo de pesquisa.
- Termos de pesquisa são palavras ou frases usadas pelos usuários para encontrar seu site em uma pesquisa.
- Salvando...
- Precisa de ajuda?
- Confiar
- Detalhes do certificado
- Não confiar
- É possível alterar o nome de usuário e a senha em configurações.
- Nenhuma mídia
- Entrar em WordPress.com
- A conta do WordPress.com atual (%s) não tem acesso às estatísticas do blog. Deseja entrar com uma conta diferente?
- O nome de usuário ou a senha digitada está incorreta. Tem um site auto-hospedado? Toque em "%s" e adicione a URL.
- Visite a central de ajuda para obter respostas sobre as perguntas mais comuns e visite os fóruns para fazer novas perguntas
- Dica: Arraste para baixo para atualizar
- Carregando posts...
- Carregando páginas...
- Ajuda
- Perdeu a senha?
- Alterar visibilidade do blog
- Fóruns
- Central de ajuda
- Certificado SSL inválido
- Não há blogs WordPress.com visíveis para os quais reblogar este post
- Se você geralmente se conecta a este site sem problemas, talvez este erro signifique que alguém está tentando imitar o site, e você não deve continuar. Deseja confiar no certificado mesmo assim?
- Sem notificações
- Dispositivo sem memória
- Você não está seguindo nenhum blog ainda
- Não há rede disponível
- Não foi possível remover o blog
- O item de mídia não pode ser recuperado
- Ocorreu um erro ao acessar este blog
- Aguarde até que o upload esteja completo
- Sem comentários
- Não é spam
- Não foram encontrados resultados
- Tenha certeza que você tem privilégio para definir um tema
- Editar
- Não foi possível se conectar ao site do WordPress
- Problema ao adicionar categoria
- Nenhum post visualizado
- Nenhum post visualizado
- Nenhuma estatística disponível
- Verifique se a URL do blog inserida é válida
- Ocorreu um erro
- Ocorreu um erro ao editar o comentário
- Não foi possível carregar o comentário
- PIN errado
- Nome de usuário não permitido
- Categoria adicionada com êxito
- O campo de nome da categoria é obrigatório
- O nome da categoria %1$s não é válido. Ela foi renomeada para %2$s.
- Não foi encontrada uma conta WordPress, adicione uma conta e tente novamente
- Configurações de autocompartilhamento redefinidas
- O blog selecionado previamente não é mais visível
- Nenhuma mídia neste intervalo de tempo
- Falha ao buscar temas
- Falha ao definir tema
- Um cartão SD montado é necessário para fazer upload de mídia
- Ocorreu um erro na resposta
- Nenhuma página ou post com tags foi visualizado
- Nenhum vídeo foi reproduzido
- Nenhum comentário
- Ocorreu um erro ao excluir o(a) %s
- Não foi possível atualizar os posts no momento
- Não foi possível atualizar as páginas no momento
- Não foi possível atualizar as notificações no momento
- Não foi possível atualizar os comentários no momento
- Não foi possível atualizar as estatísticas no momento
- Ocorreu um erro ao obter os dados
- Ocorreu um erro ao moderar
- Ocorreu um erro ao carregar o(a) %s
- Erro ao baixar a imagem
- Nenhum post com essa tag
- Você não segue uma tag
- Você não curtiu um post
- Seu endereço de email é inválido
- A senha deve ter 4 caracteres no mínimo
- O nome de usuário deve ter mais de 4 caracteres
- O nome de usuário deve ter 61 caracteres no máximo
- Ao criar uma conta, você concorda com os fascinantes %1$sTermos de Serviço%2$s
- O nome de usuário pode ter apenas letras minúsculas (a-z) e números
- Digite um nome de usuário
- O nome de usuário deve ter 4 caracteres no mínimo
- O nome de usuário não pode ter o caractere “_”
- O nome de usuário deve ter pelo menos uma letra (a-z)
- Digite um endereço de email válido
- O endereço de email não é permitido
- O nome de usuário já existe
- O endereço de email já está em uso
- No momento, o nome de usuário está reservado, mas pode estar disponível em alguns dias
- Digite um endereço de site
- O endereço do site não é permitido
- O endereço do site deve ter 4 caracteres no mínimo
- O endereço do site 64 caracteres
- O endereço do site não deve ter o caractere “_”
- Não é possível usar esse endereço de site
- O endereço do site deve ter apenas letras minúsculas (a-z) e números
- O site já existe
- O site está reservado
- No momento, o site está reservado, mas pode estar disponível em alguns dias
- O nome de usuário ou a senha digitada está incorreta
- Não é possível conectar
- Selecionar categorias
- Detalhes da conta
- Mostrar tudo
- Ocultar tudo
- Editar página
- Editar post
- Adicionar comentário
- Erro de conexão
- Cancelar edição
- Ocorreu um erro ao carregar o post. Atualize seus posts e tente novamente.
- Saiba mais
- Configurações da galeria
- Ordem da imagem
- Número de colunas
- Editar galeria
- Tema atual
- Criar um link
- Configurações de página
- Rascunho local
- Alinhamento horizontal
- Excluir post
- Excluir página
- Aprovado
- Pendente
- Spam
- Editar comentário
- Aprovar
- Spam
- Aprovando
- Marcando como spam
- Nome do autor
- URL do autor
- Comentário
- Salvando alterações
- Remover blog
- Blog removido com êxito
- Excluir rascunho
- Visualizar página
- Página excluída com êxito
- Visualizar post
- Post excluído com êxito
- Comentário adicionado com êxito
- Adicionar nova categoria
- Selecionar um blog
- Novo post
- Biblioteca de mídia
- Visualizações por país
- Principais posts e páginas
- Principais autores
- Reproduções de vídeo
- Termos do mecanismo de busca
- Nome do vídeo
- Melhor de todos
- Todo o tempo
- Análise pendente
- Credenciais HTTP (opcional)
- Novo post
- Nova página
- Nova mídia
- Foto rápida
- Vídeo rápido
- Política de privacidade
- Configurações de imagem
- Endereço do blog
- Log do aplicativo
- Compartilhar link
- Selecionar um blog
- Campo obrigatório
- Endereço de site inválido
- Não foi possível conectar. Digite o caminho completo para xmlrpc.php em seu site e tente novamente.
- Visite as configurações de segurança
- Mostrar/ocultas os blogs WordPress.com
- Insira um valor de largura dimensionado válido
- Grade de miniaturas
- Você não tem permissões para visualizar a biblioteca de mídia
- No momento, não é possível excluir algumas mídias. Tente novamente mais tarde.
- Visualização ao vivo
- Tema Premium
- Vincular texto (opcional)
- Ainda sem páginas. Por que não criar um?
- Ainda sem posts. Por que não criar um?
- Falha ao carregar
- Não foi possível encontrar o arquivo de mídia para carregar. Ele foi excluído ou movido?
- Configurações do post
- Na lixeira
- Desaprovar
- Lixeira
- Desaprovando
- Enviando para a lixeira
- Enviar para a lixeira?
- Lixeira
- Não é lixo
- Lixeira
- Email do autor
- Cancelar edição do comentário?
- Comentário obrigatório
- Comentário não foi alterado
- Status do post não foi publicado
- Status da página não foi publicado
- Exibir no navegador
- Nome da categoria
- Slug da categoria (opcional)
- Descrição da categoria (opcional)
- Categoria secundária (opcional):
- Não foi possível criar um arquivo temporário para carregar a mídia. Certifique-se de que há espaço livro suficiente em seu dispositivo.
- Localização desconhecida
- Abrir licenças da origem
- Blogs WordPress.com
- Blogs auto-hospedados
- Totais, seguidores e compartilhamentos
- Tags e categorias
- Autorização obrigatória
- Formato do post
- Exibir site
- Exibir administrador
- Alterações de local
- Blog WordPress
- Este blog está oculto e não foi possível carregá-lo. Habilite-o em configurações e tente novamente.
- Ocorreu um erro ao criar o banco de dados do aplicativo. Tente reinstalar o aplicativo.
- O plug-in Jetpack é necessário para obter estatísticas. Contate o administrador do site.
- Não é possível acessar as estatísticas
- Não é possível adicionar a tag
- Não é possível remover a tag
- Criar um blog WordPress.com
- Endereço de email
- Nome de usuário ou email
- Seu endereço auto-hospedado (URL)
- Não é possível usar esse endereço de email para se inscrever. Esse provedor bloqueia alguns de nossos emails. Use outro provedor de email.
- Este endereço de email já está em uso Verifique se há um email de ativação na sua caixa de entrada. Se você não ativar, poderá tentar novamente em alguns dias.
- O endereço do site deve ter pelo menos uma letra (a-z)
- Título do site inválido
- Excluindo página
- Excluindo post
- Compartilhar post
- Compartilhar página
- Compartilhar link
- Criando sua conta
- Criando seu site
- Criar uma conta no WordPress.com
- Alguma coisa deu errado durante a atualização da biblioteca de mídia. Tente novamente mais tarde.
- Coletando posts...
- Você e %,d outros curtiram isso
- %,d pessoas curtiram isso
- Não foi possível recuperar esse comentário
- Responder
- ©2014 Automattic, Inc
- Vídeo
- Fazendo download de mídia
- Você não pode compartilhar no WordPress sem um blog visível
- Compartilhar no WordPress
- Redefinir configurações de auto-compartilhamento
- Sempre usar essa configuração
- Ver estatísticas
- Mas não se preocupe, apenas toque no ícone da tag para começar a explorar!
- Selecione o tempo
- Você e mais um gostaram disso
- Selecione a data
- Selecione a foto
- Essa conta possui a autenticação em duas etapas ativada. Visite suas configurações de segurança no WordPress.com e gere uma senha específica para aplicativos.
- Adicionar blog hospedado
- Selecionar vídeo
- Não foi possível recuperar esse post
- Validando dados de usuário
- Validando dados do site
- Você precisa de uma senha mais segura. Use mais de 7 caracteres, misture letras maiúsculas com minúsculas, números ou caracteres especiais.
- Continuar
- Criar conta
- Adicionar site hospedado
- Entrar no WordPress.com
- Adicionar à biblioteca de mídia
- Criar galeria
- Selecione a partir de biblioteca de mídia
- O plugin Jetpack é necessário para as estatísticas. Você quer instalar o Jetpack?
- Plugin Jetpack não encontrado
- Reblogar
- Seguir
- agora
- Compartilhar
- Excluído %s
- Uma pessoa curte isto
- Você curte isto
- Post foi reblogado
- Não foi possível postar o seu comentário
- Não foi possível reblogar este post
- Você já reblogou este post
- Você já segue essa tag
- Esta tag não é válida
- Não é possível compartilhar
- Não é possível visualizar a imagem
- Não é possível abrir %s
- Adicionado
- (Sem título)
- Compartilhado de %s
- Tags
- Seguindo
- Adicione seu comentário
- Conectando ao WordPress.com
- Responda para comentar
- Iniciar Tutorial
- Nome de usuário inválido
- Limite atingido. Você pode tentar novamente em 1 minuto. Tentando novamente antes só vai aumentar o tempo de espera antes do impedimento ser levantado. Se você acha que isso é um erro, contate o suporte.
- Esta lista está vazia
- Nenhuma tag popular
- inclui %sDivulgar
- Imagens
- Tudo
- Desanexado
- Capturar foto
- Capturar vídeo
- Temas
- Título
- Legenda
- Descrição
- Tipo
- Escreva a descrição aqui
- Atualizado
- Falha na atualização
- Detalhes
- Adicionar para...
- Cliques
- Comentários
- Hoje
- Referências
- Ontem
- Dias
- Semanas
- Meses
- Sumário
- Pais
- Titulo
- URL
- Tópico
- Autor
- Vistas
- Cliques
- Referências
- Tocados
- Comentários
- Visitantes
- Hoje
- Conteúdo
- Seguidores
- Compartilhamentos
- Postagens
- Categorias
- Tags
- Blog
- Comentários
- Compartilhamentos
- COMENTÁRIOS
- Por mês
- Total
- Impressões
- Banda
- Quadrados
- Circulos
- Slideshow
- Digite uma legenda aqui
- Digite um título aqui
- Ao contrário
- Resumo
- Compartilhar
- Status
- Ver site completo
- Você tem certeza que quer excluir esses itens?
- Ativado
- Ativando
- Recursos
- Tem certeza que quer excluir esse item de mídia?
- Mosaico
- Videopress não esta disponivel
- Entre seu PIN
- Entre seu PIN antigo
- Entre seu PIN novamente
- Mudar PIN
- Definir PIN
- Data personalizada
- Aleatório
- Busca
- Busca
- Falhou ao obter temas
- Tema configurado com sucesso!
- Baseado nos 1000 comentários mais recentes
- Posts
- Mais comentados
- Desligar o bloqueio do código PIN
- Ligar o bloqueio do código PIN
- Código PIN
- Gerenciar seu código PIN.
- Mais ativos do dia
- Comentários recentes
- Visualizações
- Comentários mais ativo do dia
- Obtenha o VideoPress atualizado para fazer upload de vídeos!
- Estatísticas consolidadas para %s
- Comentar
- Você fez alterações em um post que ainda não foi enviado. Prossiga com a atualização e substitua as alterações locais?
- Enviar
- Gerenciar
- Notificações
- Gerenciar notificações
- Ativar notificações
- Tipos
- Opções
- Resposta publicada
- %d novas notificações
- e %d mais.
- Seguir ou deixar de seguir esse blog
- Entrar
- Tem certeza que deseja sair?
- Sair
- Entrando...
- Desativar
- Desativar por 1 hora
- Desativar por 8 horas
- Carregando...
- Usuário HTTP
- Senha HTTP
- Assinatura
- Adicionar uma assinatura a novos posts
- Ocorreu um erro durante o envio de mídia
- Conteúdo (Toque para adicionar texto e mídia)
- Publicar
- Adicionar mídia
- Usuário ou senha incorreta.
- Senha
- Usuário
- Leitor
- Incluir imagem no conteúdo do post
- Páginas
- Legenda (opcional)
- Largura
- Posts
- Anônimo
- Página
- Post
- Não há rede disponível
- Usar uma Imagem Destacada
- OK
- usrenamedoblog
- Largura da imagem
- Enviar e link para a imagem redimensionada
- Remover
- Agendado
- URL
- Publicado do WordPress para Android
- Enviando
- Versão
- Termos de Serviço
- WordPress para Android
- Editor: Automattic, Inc
- Sobre
- Largura padrão de imagem
- Alinhamento
- Atualizar
- Sem Título
- Editar
- Post
- Página
- Senha (opcional)
- Imediatamente
- Definir o nome do atalho
- Configurações
- Compartilhar URL
- Selecione blog para atalho QuickPress
- Nome do atalho não pode ser vazio
- Publicar
- Rascunho
- Privado
- Fazer upload e obter link para a imagem no tamanho original
- Título
- Tags (separe tags com vírgulas)
- Categorias
- Estatísticas
- Você tem certeza que deseja apagar este post
- Você tem certeza que deseja apagar a página
- Tocar som de notificação
- Vibrar
- Piscar luz de notificação
- Status
- Localização
- Cartão SD necessário
- Selecionar um vídeo da galeria
- Mídia
- Excluir
- Nenhum
- Você têm certeza que deseja remover este blog?
- Blogs
- Selecione uma foto da galeria
- Sim
- Não
- Erro
- Cancelar
- Salvar
- Adicionar
- Comentários
- Erro na atualização da categoria
- em
- Tem certeza que quer apagar o rascunho
- Responder
- Pré-visualizar
-
- - Nenhum
- - Esquerda
- - Centralizado
- - Direita
-
-
- - Nota
- - Áudio
- - Chat
- - Galeria
- - Imagem
- - Link
- - Citação
- - Padrão
- - Status
- - Vídeo
-
-
diff --git a/libs/networking/WordPressNetworking/src/main/java/org/wordpress/android/networking/RestClientUtils.java b/libs/networking/WordPressNetworking/src/main/java/org/wordpress/android/networking/RestClientUtils.java
index 8b3e4e53170c..f7a691620c1c 100644
--- a/libs/networking/WordPressNetworking/src/main/java/org/wordpress/android/networking/RestClientUtils.java
+++ b/libs/networking/WordPressNetworking/src/main/java/org/wordpress/android/networking/RestClientUtils.java
@@ -76,11 +76,11 @@ public void replyToComment(String reply, String path, Listener listener, ErrorLi
*
* https://developer.wordpress.com/docs/api/1/post/sites/%24site/posts/%24post_ID/replies/new/
*/
- public void replyToComment(String siteId, String commentId, String content, Listener listener,
+ public void replyToComment(long siteId, long commentId, String content, Listener listener,
ErrorListener errorListener) {
Map params = new HashMap();
params.put(COMMENT_REPLY_CONTENT_FIELD, content);
- String path = String.format("sites/%s/comments/%s/replies/new", siteId, commentId);
+ String path = String.format("sites/%d/comments/%d/replies/new", siteId, commentId);
post(path, params, null, listener, errorListener);
}
@@ -149,6 +149,34 @@ public void moderateComment(String siteId, String commentId, String status, List
post(path, params, null, listener, errorListener);
}
+ /**
+ * Edit the content of a comment
+ */
+ public void editCommentContent(long siteId, long commentId, String content, Listener listener,
+ ErrorListener errorListener) {
+ Map params = new HashMap();
+ params.put("content", content);
+ String path = String.format("sites/%d/comments/%d/", siteId, commentId);
+ post(path, params, null, listener, errorListener);
+ }
+
+ /**
+ * Like or unlike a comment.
+ */
+ public void likeComment(String siteId, String commentId, boolean isLiked, Listener listener,
+ ErrorListener errorListener) {
+ Map params = new HashMap();
+ String path = String.format("sites/%s/comments/%s/likes/", siteId, commentId);
+
+ if (!isLiked) {
+ path += "mine/delete";
+ } else {
+ path += "new";
+ }
+
+ post(path, params, null, listener, errorListener);
+ }
+
/**
* Get all a site's themes
*/
diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/JSONUtil.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/JSONUtil.java
index 199fba703db0..371ac6660bc0 100644
--- a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/JSONUtil.java
+++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/JSONUtil.java
@@ -77,7 +77,6 @@ public static U queryJSON(JSONObject source, String query, U defaultObject)
AppLog.e(T.UTILS, "Unable to cast the object to " + defaultObject.getClass().getName(), e);
return defaultObject;
} catch (JSONException e) {
- AppLog.e(T.UTILS, "Unable to get the Key from the input object. Key:" + query, e);
return defaultObject;
}
}
@@ -133,7 +132,6 @@ public static U queryJSON(JSONArray source, String query, U defaultObject){
AppLog.e(T.UTILS, "Unable to cast the object to "+defaultObject.getClass().getName(), e);
return defaultObject;
} catch (JSONException e) {
- AppLog.e(T.UTILS, "Unable to get the Key from the input object. Key:" + query, e);
return defaultObject;
}
}