From 6c21cada5a0b30fcee2b98871994d40d2571970d Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Wed, 11 Jun 2014 12:50:56 -0700 Subject: [PATCH 001/144] Initial commit of new model testing and UI updates. --- AndroidManifest.xml | 2 +- assets/fonts/Noticons-Regular.otf | Bin 0 -> 17780 bytes res/drawable/list_divider_full_width.xml | 8 + res/drawable/shape_oval_blue.xml | 4 + res/drawable/shape_oval_grey.xml | 4 + res/layout/note_block_basic.xml | 37 +++++ res/layout/note_block_user.xml | 74 +++++++++ res/layout/note_list_item.xml | 74 --------- res/layout/notifications_activity.xml | 16 ++ res/layout/notifications_detail_header.xml | 46 +++--- res/layout/notifications_detail_list.xml | 16 ++ res/layout/notifications_list_item.xml | 77 +++++++++ res/values/colors.xml | 7 + res/values/dimens.xml | 4 +- res/values/ids.xml | 1 + .../wordpress/android/GCMIntentService.java | 14 +- src/org/wordpress/android/models/Note.java | 86 ++++------ .../android/ui/WPActionBarActivity.java | 10 +- .../ui/notifications/BigBadgeFragment.java | 118 ------------- .../ui/notifications/DetailHeader.java | 81 --------- .../android/ui/notifications/FollowRow.java | 4 +- ...ity.java => NewNotificationsActivity.java} | 41 ++--- ...java => NewNotificationsListFragment.java} | 98 ++++++++--- .../NoteCommentLikeFragment.java | 102 ------------ .../ui/notifications/NoteFollowAdapter.java | 2 +- .../ui/notifications/NoteMatcherFragment.java | 77 --------- .../NoteSingleLineListFragment.java | 72 -------- .../notifications/NotificationFragment.java | 2 +- .../ui/notifications/NotificationUtils.java | 51 +++++- .../NotificationsDetailListFragment.java | 156 ++++++++++++++++++ ...otesAdapter.java => TestNotesAdapter.java} | 81 ++++----- .../ui/notifications/blocks/BlockType.java | 7 + .../ui/notifications/blocks/NoteBlock.java | 143 ++++++++++++++++ .../blocks/NoteBlockClickableSpan.java | 100 +++++++++++ .../notifications/blocks/NoteBlockIdType.java | 30 ++++ .../blocks/NoteBlockLinkMovementMethod.java | 69 ++++++++ .../notifications/blocks/UserNoteBlock.java | 121 ++++++++++++++ .../android/ui/posts/PostsActivity.java | 8 +- .../android/util/SimperiumUtils.java | 26 ++- src/org/wordpress/android/util/UrlUtils.java | 12 +- .../android/widgets/NoticonTextView.java | 28 ++++ .../android/widgets/TypefaceCache.java | 12 ++ 42 files changed, 1192 insertions(+), 729 deletions(-) create mode 100644 assets/fonts/Noticons-Regular.otf create mode 100644 res/drawable/list_divider_full_width.xml create mode 100644 res/drawable/shape_oval_blue.xml create mode 100644 res/drawable/shape_oval_grey.xml create mode 100644 res/layout/note_block_basic.xml create mode 100644 res/layout/note_block_user.xml delete mode 100644 res/layout/note_list_item.xml create mode 100644 res/layout/notifications_activity.xml create mode 100644 res/layout/notifications_detail_list.xml create mode 100644 res/layout/notifications_list_item.xml delete mode 100644 src/org/wordpress/android/ui/notifications/BigBadgeFragment.java delete mode 100644 src/org/wordpress/android/ui/notifications/DetailHeader.java rename src/org/wordpress/android/ui/notifications/{NotificationsActivity.java => NewNotificationsActivity.java} (91%) rename src/org/wordpress/android/ui/notifications/{NotificationsListFragment.java => NewNotificationsListFragment.java} (73%) delete mode 100644 src/org/wordpress/android/ui/notifications/NoteCommentLikeFragment.java delete mode 100644 src/org/wordpress/android/ui/notifications/NoteMatcherFragment.java delete mode 100644 src/org/wordpress/android/ui/notifications/NoteSingleLineListFragment.java create mode 100644 src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java rename src/org/wordpress/android/ui/notifications/{NotesAdapter.java => TestNotesAdapter.java} (59%) create mode 100644 src/org/wordpress/android/ui/notifications/blocks/BlockType.java create mode 100644 src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java create mode 100644 src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java create mode 100644 src/org/wordpress/android/ui/notifications/blocks/NoteBlockIdType.java create mode 100644 src/org/wordpress/android/ui/notifications/blocks/NoteBlockLinkMovementMethod.java create mode 100644 src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java create mode 100644 src/org/wordpress/android/widgets/NoticonTextView.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 53b27b5fca65..e69db4047225 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -229,7 +229,7 @@ + android:name=".ui.notifications.NewNotificationsActivity"/> &hArCC|`UCH^szdiRpSL5B8ojzyIoO9;P84q+g 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 literal 0 HcmV?d00001 diff --git a/res/drawable/list_divider_full_width.xml b/res/drawable/list_divider_full_width.xml new file mode 100644 index 000000000000..a785c69dfc9d --- /dev/null +++ b/res/drawable/list_divider_full_width.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/res/drawable/shape_oval_blue.xml b/res/drawable/shape_oval_blue.xml new file mode 100644 index 000000000000..6924c28e039f --- /dev/null +++ b/res/drawable/shape_oval_blue.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/drawable/shape_oval_grey.xml b/res/drawable/shape_oval_grey.xml new file mode 100644 index 000000000000..a9f2db013ebf --- /dev/null +++ b/res/drawable/shape_oval_grey.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/layout/note_block_basic.xml b/res/layout/note_block_basic.xml new file mode 100644 index 000000000000..38ac0269fd75 --- /dev/null +++ b/res/layout/note_block_basic.xml @@ -0,0 +1,37 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/layout/note_block_user.xml b/res/layout/note_block_user.xml new file mode 100644 index 000000000000..450df12dd2ec --- /dev/null +++ b/res/layout/note_block_user.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/note_list_item.xml b/res/layout/note_list_item.xml deleted file mode 100644 index acf976a49523..000000000000 --- a/res/layout/note_list_item.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/notifications_activity.xml b/res/layout/notifications_activity.xml new file mode 100644 index 000000000000..8e7db7aed358 --- /dev/null +++ b/res/layout/notifications_activity.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/res/layout/notifications_detail_header.xml b/res/layout/notifications_detail_header.xml index 037f8f3a1324..bf575911283d 100644 --- a/res/layout/notifications_detail_header.xml +++ b/res/layout/notifications_detail_header.xml @@ -1,29 +1,29 @@ - + android:layout_height="match_parent" + android:orientation="horizontal" + android:padding="@dimen/margin_extra_large" + android:background="@color/white" + android:gravity="center_vertical"> + + - - + android:textSize="@dimen/text_sz_medium" + android:textColor="@color/notifications_blue" /> - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/notifications_detail_list.xml b/res/layout/notifications_detail_list.xml new file mode 100644 index 000000000000..dfa6f7b64950 --- /dev/null +++ b/res/layout/notifications_detail_list.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/notifications_list_item.xml b/res/layout/notifications_list_item.xml new file mode 100644 index 000000000000..c07c74f5d0ca --- /dev/null +++ b/res/layout/notifications_list_item.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index 66c726e09ee5..e40f4167705e 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -101,4 +101,11 @@ #646464 + + + #00aadc + #324155 + #b2c6d3 + #90aec2 + #e8f0f5 diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 2d39727f1696..bf944fdb9985 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -99,8 +99,8 @@ 600dp - 20dp - 48dp + 28dp + 70dp 3dp 8dp diff --git a/res/values/ids.xml b/res/values/ids.xml index dc3d67526b88..fead989ec40d 100644 --- a/res/values/ids.xml +++ b/res/values/ids.xml @@ -11,4 +11,5 @@ + \ No newline at end of file diff --git a/src/org/wordpress/android/GCMIntentService.java b/src/org/wordpress/android/GCMIntentService.java index 0dbc98641d21..19ce8d5b6057 100644 --- a/src/org/wordpress/android/GCMIntentService.java +++ b/src/org/wordpress/android/GCMIntentService.java @@ -17,8 +17,8 @@ import com.google.android.gcm.GCMBaseIntentService; import org.apache.commons.lang.StringEscapeUtils; +import org.wordpress.android.ui.notifications.NewNotificationsActivity; import org.wordpress.android.ui.notifications.NotificationUtils; -import org.wordpress.android.ui.notifications.NotificationsActivity; import org.wordpress.android.ui.posts.PostsActivity; import org.wordpress.android.ui.prefs.UserPrefs; import org.wordpress.android.util.AppLog; @@ -146,7 +146,7 @@ protected void onMessage(Context context, Intent intent) { | Intent.FLAG_ACTIVITY_CLEAR_TASK); resultIntent.setAction("android.intent.action.MAIN"); resultIntent.addCategory("android.intent.category.LAUNCHER"); - resultIntent.putExtra(NotificationsActivity.FROM_NOTIFICATION_EXTRA, true); + resultIntent.putExtra(NewNotificationsActivity.FROM_NOTIFICATION_EXTRA, true); if (mActiveNotificationsMap.size() <= 1) { mBuilder = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.notification_icon).setContentTitle(title) @@ -154,7 +154,7 @@ protected void onMessage(Context context, Intent intent) { .setStyle(new NotificationCompat.BigTextStyle().bigText(message)); if (note_id != null) { - resultIntent.putExtra(NotificationsActivity.NOTE_ID_EXTRA, note_id); + resultIntent.putExtra(NewNotificationsActivity.NOTE_ID_EXTRA, note_id); } // Add some actions if this is a comment notification @@ -166,10 +166,10 @@ protected void onMessage(Context context, Intent intent) { commentReplyIntent.setAction("android.intent.action.MAIN"); commentReplyIntent.addCategory("android.intent.category.LAUNCHER"); commentReplyIntent.addCategory("comment-reply"); - commentReplyIntent.putExtra(NotificationsActivity.FROM_NOTIFICATION_EXTRA, true); - commentReplyIntent.putExtra(NotificationsActivity.NOTE_INSTANT_REPLY_EXTRA, true); + commentReplyIntent.putExtra(NewNotificationsActivity.FROM_NOTIFICATION_EXTRA, true); + commentReplyIntent.putExtra(NewNotificationsActivity.NOTE_INSTANT_REPLY_EXTRA, true); if (note_id != null) { - commentReplyIntent.putExtra(NotificationsActivity.NOTE_ID_EXTRA, note_id); + commentReplyIntent.putExtra(NewNotificationsActivity.NOTE_ID_EXTRA, note_id); } PendingIntent commentReplyPendingIntent = PendingIntent.getActivity(context, 0, commentReplyIntent, @@ -245,7 +245,7 @@ protected void onMessage(Context context, Intent intent) { public void broadcastNewNotification(Context context) { Intent msgIntent = new Intent(); - msgIntent.setAction(NotificationsActivity.NOTIFICATION_ACTION); + msgIntent.setAction(NewNotificationsActivity.NOTIFICATION_ACTION); LocalBroadcastManager.getInstance(context).sendBroadcast(msgIntent); } diff --git a/src/org/wordpress/android/models/Note.java b/src/org/wordpress/android/models/Note.java index 76c84bb85d34..7226640148b0 100644 --- a/src/org/wordpress/android/models/Note.java +++ b/src/org/wordpress/android/models/Note.java @@ -3,7 +3,7 @@ */ package org.wordpress.android.models; -import android.text.Html; +import android.text.Spannable; import android.text.TextUtils; import android.util.Log; @@ -13,6 +13,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.wordpress.android.ui.notifications.NotificationUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.DateTimeUtils; @@ -78,16 +79,7 @@ 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"; @@ -112,7 +104,7 @@ public static enum EnabledActions { private long mTimestamp; private transient String mCommentPreview; - private transient String mSubject; + private Spannable mSubject; private transient String mIconUrl; private transient String mSnippet; private transient String mNoteType; @@ -146,15 +138,12 @@ public JSONObject toJSONObject() { } 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; @@ -168,28 +157,27 @@ public Boolean isCommentType() { return isType(NOTE_COMMENT_TYPE); } - public Boolean isCommentLikeType() { - return isType(NOTE_COMMENT_LIKE_TYPE); - } - public Boolean isAutomattcherType() { return isType(NOTE_MATCHER_TYPE); } - public String getSubject() { + public Spannable getSubject() { if (mSubject == null) { - String text = queryJSON("subject.text", "").trim(); - if (text.equals("")) { - text = queryJSON("subject.html", ""); + try { + JSONObject subject = mNoteJSON.getJSONObject("subject"); + mSubject = NotificationUtils.getSpannableTextFromIndices(subject, false, null); + } catch (JSONException e) { + e.printStackTrace(); } - mSubject = Html.fromHtml(text).toString(); + } return mSubject; } public String getIconURL() { - if (mIconUrl == null) - mIconUrl = queryJSON("subject.icon", ""); + if (mIconUrl==null) + mIconUrl = queryJSON("icon", ""); + return mIconUrl; } @@ -224,29 +212,23 @@ 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) { + + 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)); @@ -272,20 +254,17 @@ public String getTimeSpan() { return DateTimeUtils.timestampToTimeSpan(getTimestamp()); } - String getTemplate() { - return queryJSON("body.template", ""); - } - - public Boolean isMultiLineListTemplate() { - return getTemplate().equals(MULTI_LINE_LIST_TEMPLATE); - } - - public Boolean isSingleLineListTemplate() { - return getTemplate().equals(SINGLE_LINE_LIST_TEMPLATE); + public 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 + public String getNoticonCharacter() { + return queryJSON("noticon", ""); } Map getActions() { @@ -369,7 +348,10 @@ void preloadContent() { return; } - if (isCommentType()) { + /*if (isCommentType()) { + // pre-load the comment HTML for being displayed. Cleans up emoticons. + mComment = HtmlUtils.fromHtml(getCommentText()); + // pre-load the preview text getCommentPreview(); } @@ -380,7 +362,7 @@ void preloadContent() { getType(); // pre-load site/post/comment IDs - preloadMetaIds(); + preloadMetaIds();*/ } /* diff --git a/src/org/wordpress/android/ui/WPActionBarActivity.java b/src/org/wordpress/android/ui/WPActionBarActivity.java index bd68699434c2..90e6f63f6972 100644 --- a/src/org/wordpress/android/ui/WPActionBarActivity.java +++ b/src/org/wordpress/android/ui/WPActionBarActivity.java @@ -45,7 +45,7 @@ import org.wordpress.android.ui.accounts.WelcomeActivity; 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.NewNotificationsActivity; import org.wordpress.android.ui.posts.EditPostActivity; import org.wordpress.android.ui.posts.PagesActivity; import org.wordpress.android.ui.posts.PostsActivity; @@ -1056,13 +1056,13 @@ public Boolean isVisible(){ } @Override public Boolean isSelected(){ - return WPActionBarActivity.this instanceof NotificationsActivity; + return WPActionBarActivity.this instanceof NewNotificationsActivity; } @Override public void onSelectItem(){ - if (!(WPActionBarActivity.this instanceof NotificationsActivity)) + if (!(WPActionBarActivity.this instanceof NewNotificationsActivity)) mShouldFinish = true; - Intent intent = new Intent(WPActionBarActivity.this, NotificationsActivity.class); + Intent intent = new Intent(WPActionBarActivity.this, NewNotificationsActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivityWithDelay(intent); } @@ -1104,7 +1104,7 @@ public void onReceive(Context context, Intent intent) { ToastUtils.showAuthErrorDialog(WPActionBarActivity.this); } if (intent.getAction().equals(SimperiumUtils.BROADCAST_ACTION_SIMPERIUM_NOT_AUTHORIZED) && - WPActionBarActivity.this instanceof NotificationsActivity) { + WPActionBarActivity.this instanceof NewNotificationsActivity) { ToastUtils.showAuthErrorDialog(WPActionBarActivity.this, R.string.sign_in_again, R.string.simperium_connection_error); } if (intent.getAction().equals(WordPress.BROADCAST_ACTION_XMLRPC_TWO_FA_AUTH)) { diff --git a/src/org/wordpress/android/ui/notifications/BigBadgeFragment.java b/src/org/wordpress/android/ui/notifications/BigBadgeFragment.java deleted file mode 100644 index cedf24cbf391..000000000000 --- a/src/org/wordpress/android/ui/notifications/BigBadgeFragment.java +++ /dev/null @@ -1,118 +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.util.HtmlUtils; -import org.wordpress.android.util.JSONUtil; -import org.wordpress.android.util.WPLinkMovementMethod; - -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(WPLinkMovementMethod.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; - - // stats activity is designed to work with the current blog, so switch blogs if necessary - if (WordPress.getCurrentRemoteBlogId() != remoteBlogId) { - // TODO: should we show a toast to let user know blog was switched? - int localBlogId = WordPress.wpDB.getLocalTableBlogIdForRemoteBlogId(remoteBlogId); - WordPress.setCurrentBlog(localBlogId); - } - - Intent intent = new Intent(getActivity(), StatsActivity.class); - intent.putExtra(StatsActivity.ARG_NO_MENU_DRAWER, true); - getActivity().startActivity(intent); - } -} \ No newline at end of file diff --git a/src/org/wordpress/android/ui/notifications/DetailHeader.java b/src/org/wordpress/android/ui/notifications/DetailHeader.java deleted file mode 100644 index ac3f079159f3..000000000000 --- a/src/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/src/org/wordpress/android/ui/notifications/FollowRow.java b/src/org/wordpress/android/ui/notifications/FollowRow.java index 7ca52b2658e5..2cd8b6d33abc 100644 --- a/src/org/wordpress/android/ui/notifications/FollowRow.java +++ b/src/org/wordpress/android/ui/notifications/FollowRow.java @@ -204,7 +204,7 @@ private void updateFollowButton(boolean isFollowing) { followButton.setText(isFollowing ? R.string.reader_btn_unfollow : R.string.reader_btn_follow); } - private class ClickListener implements View.OnClickListener { + private class ClickListener implements OnClickListener { public void onClick(View v) { if (!hasFollowListener()) { return; @@ -227,7 +227,7 @@ public void onClick(View v) { } } - private class LongClickListener implements View.OnLongClickListener { + private class LongClickListener implements OnLongClickListener { @Override public boolean onLongClick(View v) { Toast.makeText(getContext(), getResources().getString(R.string.tooltip_follow), Toast.LENGTH_SHORT).show(); diff --git a/src/org/wordpress/android/ui/notifications/NotificationsActivity.java b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java similarity index 91% rename from src/org/wordpress/android/ui/notifications/NotificationsActivity.java rename to src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java index dbb2959c0a81..3269f9d745db 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationsActivity.java +++ b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java @@ -34,7 +34,7 @@ import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.stats.AnalyticsTracker; -public class NotificationsActivity extends WPActionBarActivity +public class NewNotificationsActivity extends WPActionBarActivity implements CommentActions.OnCommentChangeListener, NotificationFragment.OnPostClickListener, NotificationFragment.OnCommentClickListener { public static final String NOTIFICATION_ACTION = "org.wordpress.android.NOTIFICATION"; @@ -45,7 +45,7 @@ public class NotificationsActivity extends WPActionBarActivity 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 NewNotificationsListFragment mNotesList; private boolean mDualPane; private int mSelectedNoteId; private boolean mHasPerformedInitialUpdate; @@ -63,7 +63,7 @@ public void onCreate(Bundle savedInstanceState) { AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATIONS_ACCESSED); } - createMenuDrawer(R.layout.notifications); + createMenuDrawer(R.layout.notifications_activity); View fragmentContainer = findViewById(R.id.layout_fragment_container); mDualPane = fragmentContainer != null && getString(R.string.dual_pane_mode).equals(fragmentContainer.getTag()); @@ -73,7 +73,7 @@ public void onCreate(Bundle savedInstanceState) { FragmentManager fm = getFragmentManager(); fm.addOnBackStackChangedListener(mOnBackStackChangedListener); - mNotesList = (NotificationsListFragment) fm.findFragmentById(R.id.fragment_notes_list); + mNotesList = (NewNotificationsListFragment) fm.findFragmentById(R.id.fragment_notes_list); mNotesList.setOnNoteClickListener(new NoteClickListener()); GCMIntentService.clearNotificationsMap(); @@ -228,16 +228,15 @@ void popNoteDetail(){ /** * 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; if (note.isCommentType()) { // show comment detail for comment notifications return CommentDetailFragment.newInstance(note); - } else if (note.isCommentLikeType()) { - return new NoteCommentLikeFragment(); } else if (note.isAutomattcherType()) { // show reader post detail for automattchers about posts - note that comment // automattchers are handled by note.isCommentType() above @@ -245,16 +244,11 @@ private Fragment getDetailFragmentForNote(Note note){ if (isPost) { return ReaderPostDetailFragment.newInstance(note.getBlogId(), note.getPostId()); } else { - // right now we'll never get here - return new NoteMatcherFragment(); + return NotificationsDetailListFragment.newInstance(note); } - } else if (note.isSingleLineListTemplate()) { - return new NoteSingleLineListFragment(); - } else if (note.isBigBadgeTemplate()) { - return new BigBadgeFragment(); + } else { + return NotificationsDetailListFragment.newInstance(note); } - - return null; } /** @@ -267,11 +261,12 @@ private void openNote(final Note note) { mSelectedNoteId = StringUtils.stringToInt(note.getId()); + // TODO // mark the note as read if it's unread - if (note.isUnread()) { + /*if (note.isUnread()) { // mark as read which syncs with simperium note.markAsRead(); - } + }*/ FragmentManager fm = getFragmentManager(); // remove the note detail if it's already on there @@ -281,16 +276,6 @@ private void openNote(final Note note) { // 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; - } - - // 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); - } // swap the fragment FragmentTransaction ft = fm.beginTransaction(); @@ -309,7 +294,7 @@ private void openNote(final Note note) { ft.commitAllowingStateLoss(); } - private class NoteClickListener implements NotificationsListFragment.OnNoteClickListener { + private class NoteClickListener implements NewNotificationsListFragment.OnNoteClickListener { @Override public void onClickNote(Note note){ if (note == null) diff --git a/src/org/wordpress/android/ui/notifications/NotificationsListFragment.java b/src/org/wordpress/android/ui/notifications/NewNotificationsListFragment.java similarity index 73% rename from src/org/wordpress/android/ui/notifications/NotificationsListFragment.java rename to src/org/wordpress/android/ui/notifications/NewNotificationsListFragment.java index 45e010246516..49346e40e705 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationsListFragment.java +++ b/src/org/wordpress/android/ui/notifications/NewNotificationsListFragment.java @@ -12,8 +12,6 @@ import android.widget.TextView; import com.simperium.client.Bucket; -import com.simperium.client.BucketObject; -import com.simperium.client.BucketObjectMissingException; import org.wordpress.android.R; import org.wordpress.android.models.Note; @@ -21,11 +19,14 @@ import org.wordpress.android.util.SimperiumUtils; import org.wordpress.android.util.ToastUtils; +import java.io.IOException; +import java.io.InputStream; + import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout; -public class NotificationsListFragment extends ListFragment implements Bucket.Listener { +public class NewNotificationsListFragment extends ListFragment implements Bucket.Listener { private PullToRefreshHelper mFauxPullToRefreshHelper; - private NotesAdapter mNotesAdapter; + private TestNotesAdapter mNotesAdapter; private OnNoteClickListener mNoteClickListener; private boolean mShouldLoadFirstNote; @@ -44,6 +45,41 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa return v; } + /*@Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // setup the initial notes adapter, starts listening to the bucket + mBucket = SimperiumUtils.getNotesBucket(); + + try { + JSONArray notesArray = new JSONArray(loadJSONFromAsset()); + List notesArrayList = new ArrayList(); + for (int i=0; i < notesArray.length(); i++) { + Note note = new Note(notesArray.getJSONObject(i)); + notesArrayList.add(note); + } + + mNotesAdapter = new TestNotesAdapter(getActivity(), R.layout.notifications_list_item, notesArrayList); + + ListView listView = getListView(); + listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + listView.setDivider(getResources().getDrawable(R.drawable.list_divider)); + listView.setDividerHeight(1); + listView.setBackgroundColor(getResources().getColor(R.color.white)); + setListAdapter(mNotesAdapter); + + // Set empty text if no notifications + TextView textview = (TextView) listView.getEmptyView(); + if (textview != null) { + textview.setText(getText(R.string.notifications_empty_list)); + } + + } catch (JSONException e) { + e.printStackTrace(); + } + }*/ + @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -56,7 +92,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) { listView.setDivider(getResources().getDrawable(R.drawable.list_divider)); listView.setDividerHeight(1); if (mBucket != null) { - mNotesAdapter = new NotesAdapter(getActivity(), mBucket); + mNotesAdapter = new TestNotesAdapter(getActivity(), mBucket); setListAdapter(mNotesAdapter); } else { ToastUtils.showToast(getActivity(), R.string.error_refresh_notifications); @@ -83,25 +119,21 @@ public void 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); - } + mBucket.removeListener(this); + super.onPause(); } @Override - public void onDestroyView() { - if (mNotesAdapter != null) { - mNotesAdapter.closeCursor(); - } + public void onDestroy() { + //mNotesAdapter.closeCursor(); + mFauxPullToRefreshHelper.unregisterReceiver(getActivity()); super.onDestroyView(); } @@ -151,7 +183,7 @@ public void setOnNoteClickListener(OnNoteClickListener listener) { } protected void updateLastSeenTime() { - // set the timestamp to now + /* // set the timestamp to now try { if (mNotesAdapter != null && mNotesAdapter.getCount() > 0 && SimperiumUtils.getMetaBucket() != null) { Note newestNote = mNotesAdapter.getNote(0); @@ -161,7 +193,7 @@ protected void updateLastSeenTime() { } } catch (BucketObjectMissingException e) { // try again later, meta is created by wordpress.com - } + }*/ } public void refreshNotes() { @@ -173,7 +205,7 @@ public void refreshNotes() { @Override public void run() { mNotesAdapter.reloadNotes(); - updateLastSeenTime(); + /*updateLastSeenTime(); // Show first note if we're on a landscape tablet if (mShouldLoadFirstNote && mNotesAdapter.getCount() > 0) { @@ -183,7 +215,7 @@ public void run() { mNoteClickListener.onClickNote(note); getListView().setItemChecked(0, true); } - } + }*/ } }); } @@ -226,4 +258,30 @@ public void setShouldLoadFirstNote(boolean shouldLoad) { private boolean hasActivity() { return getActivity() != null; } -} \ No newline at end of file + + // FOR NEW MODEL TESTING, REMOVE L8R!!! + public String loadJSONFromAsset() { + String json = null; + try { + + InputStream is = getActivity().getAssets().open("test.json"); + + int size = is.available(); + + byte[] buffer = new byte[size]; + + is.read(buffer); + + is.close(); + + json = new String(buffer, "UTF-8"); + + + } catch (IOException ex) { + ex.printStackTrace(); + return null; + } + return json; + + } +} diff --git a/src/org/wordpress/android/ui/notifications/NoteCommentLikeFragment.java b/src/org/wordpress/android/ui/notifications/NoteCommentLikeFragment.java deleted file mode 100644 index 7499ec5ccfc1..000000000000 --- a/src/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/src/org/wordpress/android/ui/notifications/NoteFollowAdapter.java b/src/org/wordpress/android/ui/notifications/NoteFollowAdapter.java index e3c785201130..b5d1f92e4c1e 100644 --- a/src/org/wordpress/android/ui/notifications/NoteFollowAdapter.java +++ b/src/org/wordpress/android/ui/notifications/NoteFollowAdapter.java @@ -96,7 +96,7 @@ private void setNote(Note note) { public View getView(int position, View cachedView, ViewGroup parent){ View view; if (cachedView == null) { - view = mInflater.inflate(R.layout.notifications_follow_row, null); + view = mInflater.inflate(R.layout.note_block_user, null); } else { view = cachedView; } diff --git a/src/org/wordpress/android/ui/notifications/NoteMatcherFragment.java b/src/org/wordpress/android/ui/notifications/NoteMatcherFragment.java deleted file mode 100644 index 773fb310952f..000000000000 --- a/src/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/src/org/wordpress/android/ui/notifications/NoteSingleLineListFragment.java b/src/org/wordpress/android/ui/notifications/NoteSingleLineListFragment.java deleted file mode 100644 index 6a6fa96a0bd5..000000000000 --- a/src/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/src/org/wordpress/android/ui/notifications/NotificationFragment.java b/src/org/wordpress/android/ui/notifications/NotificationFragment.java index a569d6223dbd..f7aea2b8c34a 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationFragment.java +++ b/src/org/wordpress/android/ui/notifications/NotificationFragment.java @@ -20,4 +20,4 @@ public static interface OnCommentClickListener { public Note getNote(); public void setNote(Note note); -} \ No newline at end of file +} diff --git a/src/org/wordpress/android/ui/notifications/NotificationUtils.java b/src/org/wordpress/android/ui/notifications/NotificationUtils.java index ee45e3a4161a..f75f625aad13 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationUtils.java +++ b/src/org/wordpress/android/ui/notifications/NotificationUtils.java @@ -2,9 +2,15 @@ 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,13 @@ 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.blocks.NoteBlock; +import org.wordpress.android.ui.notifications.blocks.NoteBlockClickableSpan; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.DeviceUtils; @@ -131,9 +140,9 @@ public static void registerDeviceForPushNotifications(final Context ctx, String 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"); @@ -166,7 +175,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"); @@ -201,4 +210,40 @@ public static String getAppPushNotificationsName(){ return "org.wordpress.android.playstore"; } + + public static Spannable getSpannableTextFromIndices(JSONObject subject, boolean shouldLinkify, + final NoteBlock.OnNoteBlockTextClickListener onNoteBlockTextClickListener) { + + String text = subject.optString("text", ""); + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text); + + try { + JSONArray idsArray = subject.getJSONArray("ids"); + + for (int i=0; i < idsArray.length(); i++) { + JSONObject idObject = (JSONObject) idsArray.get(i); + NoteBlockClickableSpan clickableSpan = new NoteBlockClickableSpan(idObject) { + @Override + public void onClick(View widget) { + 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) { + e.printStackTrace(); + } + + return spannableStringBuilder; + } } \ No newline at end of file diff --git a/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java new file mode 100644 index 000000000000..88a311c88498 --- /dev/null +++ b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java @@ -0,0 +1,156 @@ +/** + * 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.Bundle; +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 android.widget.Toast; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.wordpress.android.R; +import org.wordpress.android.models.Note; +import org.wordpress.android.ui.notifications.blocks.NoteBlock; +import org.wordpress.android.ui.notifications.blocks.NoteBlockClickableSpan; +import org.wordpress.android.ui.notifications.blocks.NoteBlockIdType; +import org.wordpress.android.ui.notifications.blocks.UserNoteBlock; +import org.wordpress.android.util.JSONUtil; +import org.wordpress.android.widgets.NoticonTextView; +import org.wordpress.android.widgets.WPTextView; + +import java.util.ArrayList; +import java.util.List; + +public class NotificationsDetailListFragment extends ListFragment implements NotificationFragment { + private Note mNote; + private List mNoteBlockArray = new ArrayList(); + + public NotificationsDetailListFragment() { + } + + public static NotificationsDetailListFragment newInstance(final Note note) { + NotificationsDetailListFragment fragment = new NotificationsDetailListFragment(); + fragment.setNote(note); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.notifications_detail_list, container, false); + } + + @Override + public void onActivityCreated(Bundle bundle) { + super.onActivityCreated(bundle); + + ListView list = getListView(); + list.setDivider(null); + list.setDividerHeight(0); + list.setHeaderDividersEnabled(false); + + if (mNote == null) { + return; + } + + // Add header if we have a subject + if (hasActivity() && mNote.getSubject() != null) { + LinearLayout headerLayout = (LinearLayout)getActivity().getLayoutInflater().inflate(R.layout.notifications_detail_header, null); + if (headerLayout != null) { + NoticonTextView noticonTextView = (NoticonTextView)headerLayout.findViewById(R.id.notification_header_icon); + noticonTextView.setText(mNote.getNoticonCharacter()); + + WPTextView subjectTextView = (WPTextView)headerLayout.findViewById(R.id.notification_header_subject); + subjectTextView.setText(mNote.getSubject()); + + getListView().addHeaderView(headerLayout); + } + } + + // Loop through the body items in this note, and create blocks for each. + JSONArray bodyArray = mNote.getBody(); + 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 (NoteBlockIdType.fromString(noteBlockTypeString) == NoteBlockIdType.USER) { + noteBlock = new UserNoteBlock(noteObject); + } else { + noteBlock = new NoteBlock(noteObject, mOnNoteBlockTextClickListener); + } + + mNoteBlockArray.add(noteBlock); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + setListAdapter(new NoteBlockAdapter(getActivity(), mNoteBlockArray)); + } + } + + @Override + public Note getNote() { + return mNote; + } + + @Override + public void setNote(Note note) { + mNote = note; + } + + private class NoteBlockAdapter extends ArrayAdapter { + + private List mNoteBlockList; + private LayoutInflater mLayoutInflater; + + NoteBlockAdapter(Context context, List noteBlocks) { + super(context, R.layout.menu_drawer_row, R.id.menu_row_title, 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()); + + return noteBlock.configureView(convertView); + } + } + + private boolean hasActivity() { + return getActivity() != null; + } + + private NoteBlock.OnNoteBlockTextClickListener mOnNoteBlockTextClickListener = new NoteBlock.OnNoteBlockTextClickListener() { + @Override + public void onNoteBlockTextClicked(NoteBlockClickableSpan clickedSpan) { + if (clickedSpan.getType() == NoteBlockIdType.SITE) { + // Launch Reader view for site + } + Toast.makeText(getActivity(), "Clicked: " + clickedSpan.getId(), Toast.LENGTH_SHORT).show(); + } + }; + +} diff --git a/src/org/wordpress/android/ui/notifications/NotesAdapter.java b/src/org/wordpress/android/ui/notifications/TestNotesAdapter.java similarity index 59% rename from src/org/wordpress/android/ui/notifications/NotesAdapter.java rename to src/org/wordpress/android/ui/notifications/TestNotesAdapter.java index e7d05f7750b6..aaf61774924b 100644 --- a/src/org/wordpress/android/ui/notifications/NotesAdapter.java +++ b/src/org/wordpress/android/ui/notifications/TestNotesAdapter.java @@ -2,13 +2,11 @@ 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.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; @@ -16,27 +14,27 @@ 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.widgets.NoticonTextView; import org.wordpress.android.widgets.WPNetworkImageView; -import java.util.HashMap; +import java.util.ArrayList; -class NotesAdapter extends CursorAdapter { +class TestNotesAdapter extends CursorAdapter { - private final int mAvatarSz; + private int mAvatarSz; + private Context mContext; + private ArrayList mNotesList; private final Query mQuery; - private final Context mContext; - NotesAdapter(Context context, Bucket bucket) { + TestNotesAdapter(Context context, Bucket bucket) { super(context, null, 0x0); mContext = context; // build a query that sorts by timestamp descending mQuery = bucket.query().order(Note.Schema.TIMESTAMP_INDEX, Query.SortType.DESCENDING); - mAvatarSz = DisplayUtils.dpToPx(context, 48); + mAvatarSz = (int)context.getResources().getDimension(R.dimen.note_avatar_sz); } public void closeCursor() { @@ -61,7 +59,7 @@ private Note getNote() { @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); @@ -80,66 +78,47 @@ public void bindView(View view, Context context, Cursor cursor) { noteViewHolder.txtLabel.setText(note.getSubject()); if (note.isCommentType()) { + noteViewHolder.txtLabel.setMaxLines(2); noteViewHolder.txtDetail.setText(note.getCommentPreview()); noteViewHolder.txtDetail.setVisibility(View.VISIBLE); + // TODO Real comment data ;) + noteViewHolder.txtDetail.setText("I can't stop drinking coffee. It's the elixir of life, I'm telling you!"); } else { + noteViewHolder.txtLabel.setMaxLines(3); noteViewHolder.txtDetail.setVisibility(View.GONE); } - noteViewHolder.txtDate.setText(note.getTimeSpan()); - String avatarUrl = PhotonUtils.fixAvatar(note.getIconURL(), mAvatarSz); noteViewHolder.imgAvatar.setImageUrl(avatarUrl, WPNetworkImageView.ImageType.AVATAR); - noteViewHolder.imgNoteIcon.setImageDrawable(getDrawableForType(note.getType())); - - noteViewHolder.unreadIndicator.setVisibility(note.isUnread() ? View.VISIBLE : View.INVISIBLE); - } - - // HashMap of drawables for note types - private final HashMap mNoteIcons = new HashMap(); - - private Drawable getDrawableForType(String noteType) { - if (mContext == null || noteType == null) - return null; - - // use like icon for comment likes - if (noteType.equals(Note.NOTE_COMMENT_LIKE_TYPE)) - noteType = Note.NOTE_LIKE_TYPE; - - Drawable icon = mNoteIcons.get(noteType); - if (icon != null) - return icon; - - int imageId = mContext.getResources().getIdentifier("note_icon_" + noteType, "drawable", mContext.getPackageName()); - if (imageId == 0) { - Log.w(AppLog.TAG, "unknown note type - " + noteType); - return null; + if (!TextUtils.isEmpty(note.getNoticonCharacter())) { + noteViewHolder.noteIcon.setText(note.getNoticonCharacter()); + if (note.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); } - icon = mContext.getResources().getDrawable(imageId); - if (icon == null) - return null; - - mNoteIcons.put(noteType, icon); - return icon; + noteViewHolder.unreadIndicator.setVisibility(note.isUnread() ? View.VISIBLE : View.INVISIBLE); } private static class NoteViewHolder { private final TextView txtLabel; private final TextView txtDetail; - private final TextView unreadIndicator; - private final TextView txtDate; + private final View unreadIndicator; private final WPNetworkImageView imgAvatar; - private final ImageView imgNoteIcon; + private final NoticonTextView noteIcon; NoteViewHolder(View view) { txtLabel = (TextView) view.findViewById(R.id.note_label); txtDetail = (TextView) view.findViewById(R.id.note_detail); - unreadIndicator = (TextView) view.findViewById(R.id.unread_indicator); - txtDate = (TextView) view.findViewById(R.id.text_date); + unreadIndicator = view.findViewById(R.id.unread_indicator); imgAvatar = (WPNetworkImageView) view.findViewById(R.id.note_avatar); - imgNoteIcon = (ImageView) view.findViewById(R.id.note_icon); + noteIcon = (NoticonTextView) view.findViewById(R.id.note_icon); } } } diff --git a/src/org/wordpress/android/ui/notifications/blocks/BlockType.java b/src/org/wordpress/android/ui/notifications/blocks/BlockType.java new file mode 100644 index 000000000000..e67802474765 --- /dev/null +++ b/src/org/wordpress/android/ui/notifications/blocks/BlockType.java @@ -0,0 +1,7 @@ +package org.wordpress.android.ui.notifications.blocks; + +public enum BlockType { + UNKNOWN, + BASIC, + USER +} diff --git a/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java b/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java new file mode 100644 index 000000000000..13e88d11a304 --- /dev/null +++ b/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java @@ -0,0 +1,143 @@ +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.View; +import android.widget.MediaController; +import android.widget.VideoView; + +import com.android.volley.toolbox.NetworkImageView; + +import org.json.JSONObject; +import org.wordpress.android.R; +import org.wordpress.android.WordPress; +import org.wordpress.android.ui.notifications.NotificationUtils; +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 String PROPERTY_MEDIA_TYPE = "type"; + private static String PROPERTY_MEDIA_URL = "url"; + + private JSONObject mNoteData; + private OnNoteBlockTextClickListener mOnNoteBlockTextClickListener; + private JSONObject mMediaItem; + + public interface OnNoteBlockTextClickListener { + public void onNoteBlockTextClicked(NoteBlockClickableSpan clickedSpan); + } + + public NoteBlock(JSONObject noteObject, OnNoteBlockTextClickListener onNoteBlockTextClickListener) { + mNoteData = noteObject; + mOnNoteBlockTextClickListener = onNoteBlockTextClickListener; + } + + public BlockType getBlockType() { + return BlockType.BASIC; + } + + public JSONObject getNoteData() { + return mNoteData; + } + + public Spannable getNoteText() { + return NotificationUtils.getSpannableTextFromIndices(mNoteData, true, mOnNoteBlockTextClickListener); + } + + public JSONObject getNoteMediaItem() { + if (mMediaItem == null) { + mMediaItem = JSONUtil.queryJSON(mNoteData, "media[0]", new JSONObject()); + } + + return mMediaItem; + } + + public int getLayoutResourceId() { + return R.layout.note_block_basic; + } + + private boolean hasMediaArray() { + return mNoteData.has("media"); + } + + public boolean hasImageMediaItem() { + return hasMediaArray() && + getNoteMediaItem().optString(PROPERTY_MEDIA_TYPE, "").startsWith("image") && + getNoteMediaItem().has(PROPERTY_MEDIA_URL); + } + + public boolean hasVideoMediaItem() { + return hasMediaArray() && + getNoteMediaItem().optString(PROPERTY_MEDIA_TYPE, "").startsWith("video") && + getNoteMediaItem().has(PROPERTY_MEDIA_URL); + } + + public View configureView(View view) { + final BasicNoteBlockHolder noteBlockHolder = (BasicNoteBlockHolder)view.getTag(); + + // Note image + if (hasImageMediaItem()) { + noteBlockHolder.mImageView.setImageUrl(mMediaItem.optString("url", ""), WordPress.imageLoader); + noteBlockHolder.mImageView.setVisibility(View.VISIBLE); + } else { + noteBlockHolder.mImageView.setVisibility(View.GONE); + } + + // Note video + if (hasVideoMediaItem()) { + noteBlockHolder.mVideoView.setVideoURI(Uri.parse(mMediaItem.optString("url", ""))); + noteBlockHolder.mVideoView.setVisibility(View.VISIBLE); + + // Attach a mediaController if we are displaying a video. + final MediaController mediaController = new MediaController(noteBlockHolder.mVideoView.getContext()); + mediaController.setMediaPlayer(noteBlockHolder.mVideoView); + + noteBlockHolder.mVideoView.setMediaController(mediaController); + mediaController.requestFocus(); + noteBlockHolder.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); + } + }); + } else { + noteBlockHolder.mVideoView.setVisibility(View.GONE); + } + + // Note text + if (!TextUtils.isEmpty(getNoteText())) { + noteBlockHolder.mTextView.setText(getNoteText()); + noteBlockHolder.mTextView.setVisibility(View.VISIBLE); + } else { + noteBlockHolder.mTextView.setVisibility(View.GONE); + } + + return view; + } + + public Object getViewHolder(View view) { + return new BasicNoteBlockHolder(view); + } + + private static class BasicNoteBlockHolder { + private final WPTextView mTextView; + private final NetworkImageView mImageView; + private final VideoView mVideoView; + + BasicNoteBlockHolder(View view) { + mTextView = (WPTextView) view.findViewById(R.id.note_text); + mTextView.setMovementMethod(new NoteBlockLinkMovementMethod()); + mImageView = (NetworkImageView) view.findViewById(R.id.note_image); + mVideoView = (VideoView) view.findViewById(R.id.note_video); + } + } +} diff --git a/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java new file mode 100644 index 000000000000..bbaafb9ca6bd --- /dev/null +++ b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java @@ -0,0 +1,100 @@ +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.util.JSONUtil; + +/** + * A clickable span that includes extra ids/urls + * Maps to an 'id' in a WordPress.com note object + */ +public class NoteBlockClickableSpan extends ClickableSpan { + private long mId; + private long mSiteId; + private NoteBlockIdType mType; + private String mUrl; + private int[] mIndices; + private boolean mPressed; + private int mBackgroundColor; + + private JSONObject mBlockData; + + public NoteBlockClickableSpan(JSONObject idData) { + mBlockData = idData; + // Same color as notifications_blue in colors.xml + mBackgroundColor = Color.parseColor("#90aec2"); + processIdData(); + } + + + private void processIdData() { + if (mBlockData != null) { + mId = JSONUtil.queryJSON(mBlockData, "id", 0); + mSiteId = JSONUtil.queryJSON(mBlockData, "site_id", 0); + mType = NoteBlockIdType.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); + } + } + } + + private boolean hasUrl() { + return !TextUtils.isEmpty(mUrl); + } + + @Override + public void updateDrawState(TextPaint textPaint) { + // Set background color + textPaint.bgColor = mPressed ? mBackgroundColor : Color.TRANSPARENT; + // No underlines + textPaint.setUnderlineText(false); + } + + // return the desired style for this id type + public int getSpanStyle() { + switch (getType()) { + case USER: + return Typeface.BOLD; + case SITE: + case POST: + return Typeface.ITALIC; + default: + return Typeface.NORMAL; + } + } + + @Override + public void onClick(View widget) { + } + + public NoteBlockIdType getType() { + return mType; + } + + public int[] getIndices() { + return mIndices; + } + + public long getId() { + return mId; + } + + public long getSiteId() { + return mSiteId; + } + + public void setPressed(boolean isPressed) { + this.mPressed = isPressed; + } +} diff --git a/src/org/wordpress/android/ui/notifications/blocks/NoteBlockIdType.java b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockIdType.java new file mode 100644 index 000000000000..235192d55632 --- /dev/null +++ b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockIdType.java @@ -0,0 +1,30 @@ +package org.wordpress.android.ui.notifications.blocks; + +import android.text.TextUtils; + +/** + * Known NoteBlock Id types + */ +public enum NoteBlockIdType { + POST, + SITE, + COMMENT, + USER, + UNKNOWN; + + public static NoteBlockIdType 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 { + return UNKNOWN; + } + } +} diff --git a/src/org/wordpress/android/ui/notifications/blocks/NoteBlockLinkMovementMethod.java b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockLinkMovementMethod.java new file mode 100644 index 000000000000..49c5140a2ee6 --- /dev/null +++ b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockLinkMovementMethod.java @@ -0,0 +1,69 @@ +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; + +/** + * Allows links to be highlighted when tapped on note blocks. + * See: http://stackoverflow.com/a/20905824/309558 + */ +public class NoteBlockLinkMovementMethod extends LinkMovementMethod { + + private NoteBlockClickableSpan mPressedSpan; + + @Override + public boolean onTouchEvent(TextView textView, Spannable spannable, 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/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java b/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java new file mode 100644 index 000000000000..d0dbc975d10c --- /dev/null +++ b/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java @@ -0,0 +1,121 @@ +package org.wordpress.android.ui.notifications.blocks; + +import android.view.View; +import android.widget.Toast; + +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.reader.ReaderUtils; +import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.StringUtils; +import org.wordpress.android.util.UrlUtils; +import org.wordpress.android.widgets.WPTextView; + +/** + * 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 { + + public UserNoteBlock(JSONObject noteObject) { + super(noteObject, null); + } + + @Override + public BlockType getBlockType() { + return BlockType.USER; + } + + @Override + public int getLayoutResourceId() { + return R.layout.note_block_user; + } + + @Override + public View configureView(View view) { + UserActionNoteBlockHolder noteBlockHolder = (UserActionNoteBlockHolder)view.getTag(); + noteBlockHolder.mNameTextView.setText(getNoteText()); + noteBlockHolder.mUrlTextView.setText(StringUtils.notNullStr(getUserUrl())); + if (hasImageMediaItem()) { + noteBlockHolder.mAvatarImageView.setImageUrl(getNoteMediaItem().optString("url", ""), WordPress.imageLoader); + } + + if (hasAction()) { + configureActionButton(noteBlockHolder.mActionButton); + noteBlockHolder.mActionButton.setVisibility(View.VISIBLE); + } else { + noteBlockHolder.mActionButton.setVisibility(View.GONE); + } + + return view; + } + + private void configureActionButton(WPTextView actionButton) { + // For now we will support the follow action + try { + boolean isFollowing = getNoteData().getJSONObject("actions").getBoolean("follow"); + ReaderUtils.showFollowStatus(actionButton, isFollowing); + } catch (JSONException e) { + AppLog.e(AppLog.T.NOTIFS, "Unexpected action button value: " + e.getMessage()); + } + } + + @Override + public Object getViewHolder(View view) { + return new UserActionNoteBlockHolder(view); + } + + private class UserActionNoteBlockHolder { + private WPTextView mNameTextView; + private WPTextView mUrlTextView; + private WPTextView mActionButton; + private NetworkImageView mAvatarImageView; + + public UserActionNoteBlockHolder(View view) { + mNameTextView = (WPTextView) view.findViewById(R.id.name); + mUrlTextView = (WPTextView) view.findViewById(R.id.url); + mActionButton = (WPTextView) view.findViewById(R.id.action_button); + mActionButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(v.getContext(), "Sweet", Toast.LENGTH_SHORT).show(); + } + }); + mAvatarImageView = (NetworkImageView) view.findViewById(R.id.avatar); + } + } + + public String getUserUrl() { + if (getNoteData() == null) return null; + + JSONArray idsArray = getNoteData().optJSONArray("ids"); + if (idsArray != null) { + for (int i=0; i < idsArray.length(); i++) { + try { + JSONObject idObject = idsArray.getJSONObject(i); + if (idObject.has("url")) { + return UrlUtils.removeUrlScheme(idObject.getString("url")); + } + } catch (JSONException e) { + AppLog.i(AppLog.T.NOTIFS, "Unexpected object in notifications ids array."); + } + } + } + + return null; + } + + // Show or hide action button + private boolean hasAction() { + if (getNoteData() == null) { + return false; + } + + return getNoteData().has("actions"); + } +} diff --git a/src/org/wordpress/android/ui/posts/PostsActivity.java b/src/org/wordpress/android/ui/posts/PostsActivity.java index 5df1cdb30775..918102652dda 100644 --- a/src/org/wordpress/android/ui/posts/PostsActivity.java +++ b/src/org/wordpress/android/ui/posts/PostsActivity.java @@ -26,7 +26,7 @@ import org.wordpress.android.models.PostStatus; import org.wordpress.android.ui.MenuDrawerItem; import org.wordpress.android.ui.WPActionBarActivity; -import org.wordpress.android.ui.notifications.NotificationsActivity; +import org.wordpress.android.ui.notifications.NewNotificationsActivity; import org.wordpress.android.ui.posts.PostsListFragment.OnPostActionListener; import org.wordpress.android.ui.posts.PostsListFragment.OnPostSelectedListener; import org.wordpress.android.ui.posts.ViewPostFragment.OnDetailPostActionListener; @@ -77,7 +77,7 @@ public void onCreate(Bundle savedInstanceState) { // Check if we came from a notification, if so let's launch NotificationsActivity Bundle extras = getIntent().getExtras(); - if (extras != null && extras.getBoolean(NotificationsActivity.FROM_NOTIFICATION_EXTRA)) { + if (extras != null && extras.getBoolean(NewNotificationsActivity.FROM_NOTIFICATION_EXTRA)) { startNotificationsActivity(extras); return; } @@ -168,7 +168,7 @@ protected void onNewIntent(Intent intent) { Bundle extras = intent.getExtras(); if (extras != null) { // Check if we came from a notification, if so let's launch NotificationsActivity - if (extras.getBoolean(NotificationsActivity.FROM_NOTIFICATION_EXTRA)) { + if (extras.getBoolean(NewNotificationsActivity.FROM_NOTIFICATION_EXTRA)) { startNotificationsActivity(extras); return; } @@ -194,7 +194,7 @@ private void startNotificationsActivity(Bundle extras) { editor.putInt(LAST_ACTIVITY_PREFERENCE, NOTIFICATIONS_ACTIVITY); editor.commit(); - Intent i = new Intent(this, NotificationsActivity.class); + Intent i = new Intent(this, NewNotificationsActivity.class); i.putExtras(extras); startActivity(i); finish(); diff --git a/src/org/wordpress/android/util/SimperiumUtils.java b/src/org/wordpress/android/util/SimperiumUtils.java index 4647bf69f9c4..b743d238edf7 100644 --- a/src/org/wordpress/android/util/SimperiumUtils.java +++ b/src/org/wordpress/android/util/SimperiumUtils.java @@ -9,6 +9,8 @@ import android.support.v4.content.LocalBroadcastManager; import com.simperium.Simperium; +import com.simperium.client.AuthException; +import com.simperium.client.AuthResponseListener; import com.simperium.client.Bucket; import com.simperium.client.BucketNameInvalid; import com.simperium.client.BucketObject; @@ -45,7 +47,7 @@ public static Simperium configureSimperium(final Context context, String token) try { mNotesBucket = mSimperium.bucket(new Note.Schema()); - mMetaBucket = mSimperium.bucket("meta"); + //mMetaBucket = mSimperium.bucket("meta"); mSimperium.setUserStatusChangeListener(new User.StatusChangeListener() { @@ -54,11 +56,11 @@ public void onUserStatusChange(User.Status status) { switch (status) { case AUTHORIZED: mNotesBucket.start(); - mMetaBucket.start(); + //mMetaBucket.start(); break; case NOT_AUTHORIZED: mNotesBucket.stop(); - mMetaBucket.stop(); + //mMetaBucket.stop(); Intent simperiumNotAuthorizedIntent = new Intent(); simperiumNotAuthorizedIntent.setAction(BROADCAST_ACTION_SIMPERIUM_NOT_AUTHORIZED); LocalBroadcastManager.getInstance(context).sendBroadcast(simperiumNotAuthorizedIntent); @@ -76,7 +78,23 @@ public void onUserStatusChange(User.Status status) { } } - authorizeUser(mSimperium, token); + //authorizeUser(mSimperium, token); + // FOR TESTING + if (mSimperium.getUser().needsAuthorization()) { + mSimperium.authorizeUser(BuildConfig.SIMPERIUM_TEST_USER, BuildConfig.SIMPERIUM_TEST_PW, new AuthResponseListener() { + @Override + public void onSuccess(User user) { + AppLog.i(AppLog.T.NOTIFS, "Signed in"); + } + + @Override + public void onFailure(User user, AuthException e) { + AppLog.i(AppLog.T.NOTIFS, "Couldn't log in"); + } + }); + } else { + mNotesBucket.start(); + } return mSimperium; } diff --git a/src/org/wordpress/android/util/UrlUtils.java b/src/org/wordpress/android/util/UrlUtils.java index ed8d3f4fcf7f..b5fd8cd976d5 100644 --- a/src/org/wordpress/android/util/UrlUtils.java +++ b/src/org/wordpress/android/util/UrlUtils.java @@ -66,6 +66,16 @@ public static String addUrlSchemeIfNeeded(String url, boolean isHTTPS) { return url; } + // Formats a URL for display + public static String removeUrlScheme(String url) { + if (url == null) { + return null; + } + + url = normalizeUrl(url); + return url.replace("http://", "").replace("https://", ""); + } + /** * normalizes a URL, primarily for comparison purposes, for example so that * normalizeUrl("http://google.com/") = normalizeUrl("http://google.com") @@ -162,4 +172,4 @@ public static boolean isValidUrlAndHostNotNull(String url) { } return true; } -} +} \ No newline at end of file diff --git a/src/org/wordpress/android/widgets/NoticonTextView.java b/src/org/wordpress/android/widgets/NoticonTextView.java new file mode 100644 index 000000000000..f56bf29c4001 --- /dev/null +++ b/src/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); + 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/src/org/wordpress/android/widgets/TypefaceCache.java b/src/org/wordpress/android/widgets/TypefaceCache.java index c4bfedf0b7ab..485dd3a42d2c 100644 --- a/src/org/wordpress/android/widgets/TypefaceCache.java +++ b/src/org/wordpress/android/widgets/TypefaceCache.java @@ -48,6 +48,18 @@ protected static Typeface getTypeface(Context context, int fontStyle, int variat } } + 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); + if (typeface != null) { + mTypefaceCache.put(typefaceName, typeface); + } + } + return mTypefaceCache.get(typefaceName); } From 7b1daf1bf2246b0a289e2e63915b715457f5b1b8 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Wed, 11 Jun 2014 15:01:38 -0700 Subject: [PATCH 002/144] Added support for following sites to UserNoteBlock --- .../NotificationsDetailListFragment.java | 13 +++++- .../notifications/blocks/UserNoteBlock.java | 45 ++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java index 88a311c88498..58f1c76bac15 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java +++ b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java @@ -24,6 +24,7 @@ import org.wordpress.android.ui.notifications.blocks.NoteBlockIdType; import org.wordpress.android.ui.notifications.blocks.UserNoteBlock; import org.wordpress.android.util.JSONUtil; +import org.wordpress.android.util.ToastUtils; import org.wordpress.android.widgets.NoticonTextView; import org.wordpress.android.widgets.WPTextView; @@ -76,6 +77,7 @@ public void onActivityCreated(Bundle bundle) { } // Loop through the body items in this note, and create blocks for each. + // TODO asynctask this work JSONArray bodyArray = mNote.getBody(); if (bodyArray != null && bodyArray.length() > 0) { for (int i=0; i < bodyArray.length(); i++) { @@ -85,7 +87,7 @@ public void onActivityCreated(Bundle bundle) { NoteBlock noteBlock; String noteBlockTypeString = JSONUtil.queryJSON(noteObject, "type", ""); if (NoteBlockIdType.fromString(noteBlockTypeString) == NoteBlockIdType.USER) { - noteBlock = new UserNoteBlock(noteObject); + noteBlock = new UserNoteBlock(noteObject, mOnSiteFollowListener); } else { noteBlock = new NoteBlock(noteObject, mOnNoteBlockTextClickListener); } @@ -153,4 +155,13 @@ public void onNoteBlockTextClicked(NoteBlockClickableSpan clickedSpan) { } }; + private UserNoteBlock.OnSiteFollowListener mOnSiteFollowListener = new UserNoteBlock.OnSiteFollowListener() { + @Override + public void onSiteFollow(boolean success) { + if (hasActivity() && !success) { + ToastUtils.showToast(getActivity(), R.string.reader_toast_err_follow_blog); + } + } + }; + } diff --git a/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java b/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java index d0dbc975d10c..9771b68894d7 100644 --- a/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java +++ b/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java @@ -1,9 +1,10 @@ package org.wordpress.android.ui.notifications.blocks; import android.view.View; -import android.widget.Toast; +import com.android.volley.VolleyError; import com.android.volley.toolbox.NetworkImageView; +import com.wordpress.rest.RestRequest; import org.json.JSONArray; import org.json.JSONException; @@ -12,6 +13,7 @@ import org.wordpress.android.WordPress; import org.wordpress.android.ui.reader.ReaderUtils; import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.JSONUtil; import org.wordpress.android.util.StringUtils; import org.wordpress.android.util.UrlUtils; import org.wordpress.android.widgets.WPTextView; @@ -21,9 +23,16 @@ * Will display an action button if available (e.g. follow button) */ public class UserNoteBlock extends NoteBlock { + private boolean mIsFollowing; + private OnSiteFollowListener mSiteFollowListener; - public UserNoteBlock(JSONObject noteObject) { + public interface OnSiteFollowListener { + public void onSiteFollow(boolean success); + } + + public UserNoteBlock(JSONObject noteObject, OnSiteFollowListener listener) { super(noteObject, null); + mSiteFollowListener = listener; } @Override @@ -58,8 +67,8 @@ public View configureView(View view) { private void configureActionButton(WPTextView actionButton) { // For now we will support the follow action try { - boolean isFollowing = getNoteData().getJSONObject("actions").getBoolean("follow"); - ReaderUtils.showFollowStatus(actionButton, isFollowing); + mIsFollowing = getNoteData().getJSONObject("actions").getBoolean("follow"); + ReaderUtils.showFollowStatus(actionButton, mIsFollowing); } catch (JSONException e) { AppLog.e(AppLog.T.NOTIFS, "Unexpected action button value: " + e.getMessage()); } @@ -83,13 +92,39 @@ public UserActionNoteBlockHolder(View view) { mActionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Toast.makeText(v.getContext(), "Sweet", Toast.LENGTH_SHORT).show(); + toggleFollow(mActionButton); } }); mAvatarImageView = (NetworkImageView) view.findViewById(R.id.avatar); } } + // Follow/unfollow a site + private void toggleFollow(final WPTextView followButton) { + mIsFollowing = !mIsFollowing; + + ReaderUtils.showFollowStatus(followButton, mIsFollowing); + + long siteId = JSONUtil.queryJSON(getNoteData(), "meta.ids.site", 0); + if (siteId > 0 && mSiteFollowListener != null) { + WordPress.getRestClientUtils().followSite(String.valueOf(siteId), new RestRequest.Listener() { + @Override + public void onResponse(JSONObject jsonObject) { + mSiteFollowListener.onSiteFollow(true); + } + }, new RestRequest.ErrorListener() { + @Override + public void onErrorResponse(VolleyError volleyError) { + mIsFollowing = !mIsFollowing; + if (followButton != null) { + ReaderUtils.showFollowStatus(followButton, mIsFollowing); + mSiteFollowListener.onSiteFollow(false); + } + } + }); + } + } + public String getUserUrl() { if (getNoteData() == null) return null; From f6b9c40450090561d2e92129b9cee2baba26a0f7 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Wed, 11 Jun 2014 15:46:26 -0700 Subject: [PATCH 003/144] Added support for showing blog preview and post detail views when certain block types are tapped. So much win. --- .../NewNotificationsActivity.java | 21 +++++++++++++++++++ .../NotificationsDetailListFragment.java | 17 +++++++++++---- .../blocks/NoteBlockClickableSpan.java | 4 ++++ .../ui/reader/ReaderPostListFragment.java | 2 +- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java index 3269f9d745db..9ae86037481c 100644 --- a/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java +++ b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java @@ -26,6 +26,7 @@ import org.wordpress.android.ui.comments.CommentDetailFragment; import org.wordpress.android.ui.comments.CommentDialogs; import org.wordpress.android.ui.reader.ReaderPostDetailFragment; +import org.wordpress.android.ui.reader.ReaderPostListFragment; import org.wordpress.android.ui.reader.actions.ReaderAuthActions; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; @@ -294,6 +295,26 @@ private void openNote(final Note note) { ft.commitAllowingStateLoss(); } + public void showBlogPreviewForSiteId(long siteId) { + ReaderPostListFragment readerPostListFragment = ReaderPostListFragment.newInstance(siteId, null); + FragmentManager fm = getFragmentManager(); + + FragmentTransaction ft = fm.beginTransaction(); + ft.add(R.id.layout_fragment_container, readerPostListFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.addToBackStack(null); + ft.commitAllowingStateLoss(); + } + + public void showPostForSiteAndPostId(long siteId, long postId) { + ReaderPostDetailFragment readerPostDetailFragment = ReaderPostDetailFragment.newInstance(siteId, postId); + FragmentManager fm = getFragmentManager(); + + FragmentTransaction ft = fm.beginTransaction(); + ft.add(R.id.layout_fragment_container, readerPostDetailFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.addToBackStack(null); + ft.commitAllowingStateLoss(); + } + private class NoteClickListener implements NewNotificationsListFragment.OnNoteClickListener { @Override public void onClickNote(Note note){ diff --git a/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java index 58f1c76bac15..2575940b0ffb 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java +++ b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java @@ -12,7 +12,6 @@ import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.Toast; import org.json.JSONArray; import org.json.JSONException; @@ -148,10 +147,20 @@ private boolean hasActivity() { private NoteBlock.OnNoteBlockTextClickListener mOnNoteBlockTextClickListener = new NoteBlock.OnNoteBlockTextClickListener() { @Override public void onNoteBlockTextClicked(NoteBlockClickableSpan clickedSpan) { - if (clickedSpan.getType() == NoteBlockIdType.SITE) { - // Launch Reader view for site + if (!hasActivity()) return; + + if (clickedSpan.shouldShowBlogPreview()) { + // Show blog preview + long siteId = (clickedSpan.getType() == NoteBlockIdType.USER) ? clickedSpan.getId() : clickedSpan.getSiteId(); + NewNotificationsActivity notificationsActivity = (NewNotificationsActivity)getActivity(); + notificationsActivity.showBlogPreviewForSiteId(siteId); + } else if (clickedSpan.getType() == NoteBlockIdType.POST) { + // Show post detail + NewNotificationsActivity notificationsActivity = (NewNotificationsActivity)getActivity(); + long siteId = clickedSpan.getSiteId(); + long postId = clickedSpan.getId(); + notificationsActivity.showPostForSiteAndPostId(siteId, postId); } - Toast.makeText(getActivity(), "Clicked: " + clickedSpan.getId(), Toast.LENGTH_SHORT).show(); } }; diff --git a/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java index bbaafb9ca6bd..a522b70a6488 100644 --- a/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java +++ b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java @@ -97,4 +97,8 @@ public long getSiteId() { public void setPressed(boolean isPressed) { this.mPressed = isPressed; } + + public boolean shouldShowBlogPreview() { + return mType == NoteBlockIdType.USER || mType == NoteBlockIdType.SITE; + } } diff --git a/src/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/src/org/wordpress/android/ui/reader/ReaderPostListFragment.java index fbd5c36c61b5..8517a61e0d89 100644 --- a/src/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/src/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -119,7 +119,7 @@ static ReaderPostListFragment newInstance(String tagName, ReaderPostListType lis /* * 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(); From 36ce6fc71ac1b2898755378d138cc121a2cdebfa Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Thu, 12 Jun 2014 10:53:45 -0700 Subject: [PATCH 004/144] Removed image and video views from note_block_basic layout. Since there's no guarantee that an image or video will be in the note, it would be a waste of memory. Instead they are created if the note block contains an image or video. --- res/layout/note_block_basic.xml | 18 ----- .../ui/notifications/TestNotesAdapter.java | 2 +- .../ui/notifications/blocks/NoteBlock.java | 70 ++++++++++++++----- 3 files changed, 54 insertions(+), 36 deletions(-) diff --git a/res/layout/note_block_basic.xml b/res/layout/note_block_basic.xml index 38ac0269fd75..1406c08c6d05 100644 --- a/res/layout/note_block_basic.xml +++ b/res/layout/note_block_basic.xml @@ -9,24 +9,6 @@ android:paddingBottom="@dimen/margin_medium" android:orientation="vertical"> - - - - Date: Thu, 12 Jun 2014 14:48:24 -0700 Subject: [PATCH 005/144] Added support for using NoteBlockClickableSpan for the Url TextView in a UserNoteBlock. --- res/layout/note_block_user.xml | 1 - src/org/wordpress/android/models/Note.java | 2 +- .../ui/notifications/NotificationUtils.java | 23 ++++++++++++++-- .../NotificationsDetailListFragment.java | 2 +- .../ui/notifications/blocks/NoteBlock.java | 6 ++++- .../blocks/NoteBlockClickableSpan.java | 4 +-- .../notifications/blocks/UserNoteBlock.java | 27 ++++++++++++++----- 7 files changed, 51 insertions(+), 14 deletions(-) diff --git a/res/layout/note_block_user.xml b/res/layout/note_block_user.xml index 450df12dd2ec..c54788bc068b 100644 --- a/res/layout/note_block_user.xml +++ b/res/layout/note_block_user.xml @@ -55,7 +55,6 @@ android:id="@+id/url" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@drawable/selectable_background_wordpress" android:ellipsize="end" android:singleLine="true" android:textColor="@color/notifications_blue" diff --git a/src/org/wordpress/android/models/Note.java b/src/org/wordpress/android/models/Note.java index 7226640148b0..63ec3d3c1ed0 100644 --- a/src/org/wordpress/android/models/Note.java +++ b/src/org/wordpress/android/models/Note.java @@ -165,7 +165,7 @@ public Spannable getSubject() { if (mSubject == null) { try { JSONObject subject = mNoteJSON.getJSONObject("subject"); - mSubject = NotificationUtils.getSpannableTextFromIndices(subject, false, null); + mSubject = NotificationUtils.getSpannableTextFromIndices(subject, null); } catch (JSONException e) { e.printStackTrace(); } diff --git a/src/org/wordpress/android/ui/notifications/NotificationUtils.java b/src/org/wordpress/android/ui/notifications/NotificationUtils.java index f75f625aad13..6e4599df5135 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationUtils.java +++ b/src/org/wordpress/android/ui/notifications/NotificationUtils.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Color; import android.graphics.Typeface; import android.os.Build; import android.preference.PreferenceManager; @@ -211,7 +212,7 @@ public static String getAppPushNotificationsName(){ return "org.wordpress.android.playstore"; } - public static Spannable getSpannableTextFromIndices(JSONObject subject, boolean shouldLinkify, + public static Spannable getSpannableTextFromIndices(JSONObject subject, final NoteBlock.OnNoteBlockTextClickListener onNoteBlockTextClickListener) { String text = subject.optString("text", ""); @@ -222,7 +223,7 @@ public static Spannable getSpannableTextFromIndices(JSONObject subject, boolean for (int i=0; i < idsArray.length(); i++) { JSONObject idObject = (JSONObject) idsArray.get(i); - NoteBlockClickableSpan clickableSpan = new NoteBlockClickableSpan(idObject) { + NoteBlockClickableSpan clickableSpan = new NoteBlockClickableSpan(idObject, Color.parseColor("#90aec2")) { @Override public void onClick(View widget) { onNoteBlockTextClickListener.onNoteBlockTextClicked(this); @@ -246,4 +247,22 @@ public void onClick(View widget) { return spannableStringBuilder; } + + public static Spannable getClickableTextForIdUrl(JSONObject idBlock, String text, final NoteBlock.OnNoteBlockTextClickListener onNoteBlockTextClickListener) { + if (idBlock == null || TextUtils.isEmpty(text)) { + return new SpannableStringBuilder(""); + } + + NoteBlockClickableSpan clickableSpan = new NoteBlockClickableSpan(idBlock, Color.parseColor("#b2c6d3")) { + @Override + public void onClick(View widget) { + onNoteBlockTextClickListener.onNoteBlockTextClicked(this); + } + }; + + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text); + spannableStringBuilder.setSpan(clickableSpan, 0, spannableStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + return spannableStringBuilder; + } } \ No newline at end of file diff --git a/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java index 2575940b0ffb..0eb5d84d8bab 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java +++ b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java @@ -86,7 +86,7 @@ public void onActivityCreated(Bundle bundle) { NoteBlock noteBlock; String noteBlockTypeString = JSONUtil.queryJSON(noteObject, "type", ""); if (NoteBlockIdType.fromString(noteBlockTypeString) == NoteBlockIdType.USER) { - noteBlock = new UserNoteBlock(noteObject, mOnSiteFollowListener); + noteBlock = new UserNoteBlock(noteObject, mOnNoteBlockTextClickListener, mOnSiteFollowListener); } else { noteBlock = new NoteBlock(noteObject, mOnNoteBlockTextClickListener); } diff --git a/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java b/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java index 21aeab98f0a0..01ffae5da92c 100644 --- a/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java +++ b/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java @@ -44,6 +44,10 @@ public NoteBlock(JSONObject noteObject, OnNoteBlockTextClickListener onNoteBlock mOnNoteBlockTextClickListener = onNoteBlockTextClickListener; } + public OnNoteBlockTextClickListener getOnNoteBlockTextClickListener() { + return mOnNoteBlockTextClickListener; + } + public BlockType getBlockType() { return BlockType.BASIC; } @@ -53,7 +57,7 @@ public JSONObject getNoteData() { } public Spannable getNoteText() { - return NotificationUtils.getSpannableTextFromIndices(mNoteData, true, mOnNoteBlockTextClickListener); + return NotificationUtils.getSpannableTextFromIndices(mNoteData, mOnNoteBlockTextClickListener); } public JSONObject getNoteMediaItem() { diff --git a/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java index a522b70a6488..96502d64b47f 100644 --- a/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java +++ b/src/org/wordpress/android/ui/notifications/blocks/NoteBlockClickableSpan.java @@ -26,10 +26,10 @@ public class NoteBlockClickableSpan extends ClickableSpan { private JSONObject mBlockData; - public NoteBlockClickableSpan(JSONObject idData) { + public NoteBlockClickableSpan(JSONObject idData, int backgroundColor) { mBlockData = idData; // Same color as notifications_blue in colors.xml - mBackgroundColor = Color.parseColor("#90aec2"); + mBackgroundColor = backgroundColor; processIdData(); } diff --git a/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java b/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java index 9771b68894d7..927d68bd43ef 100644 --- a/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java +++ b/src/org/wordpress/android/ui/notifications/blocks/UserNoteBlock.java @@ -11,10 +11,10 @@ import org.json.JSONObject; import org.wordpress.android.R; import org.wordpress.android.WordPress; +import org.wordpress.android.ui.notifications.NotificationUtils; import org.wordpress.android.ui.reader.ReaderUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.JSONUtil; -import org.wordpress.android.util.StringUtils; import org.wordpress.android.util.UrlUtils; import org.wordpress.android.widgets.WPTextView; @@ -25,13 +25,14 @@ public class UserNoteBlock extends NoteBlock { private boolean mIsFollowing; private OnSiteFollowListener mSiteFollowListener; + private long mUserSiteId; public interface OnSiteFollowListener { public void onSiteFollow(boolean success); } - public UserNoteBlock(JSONObject noteObject, OnSiteFollowListener listener) { - super(noteObject, null); + public UserNoteBlock(JSONObject noteObject, OnNoteBlockTextClickListener onNoteBlockTextClickListener, OnSiteFollowListener listener) { + super(noteObject, onNoteBlockTextClickListener); mSiteFollowListener = listener; } @@ -49,7 +50,12 @@ public int getLayoutResourceId() { public View configureView(View view) { UserActionNoteBlockHolder noteBlockHolder = (UserActionNoteBlockHolder)view.getTag(); noteBlockHolder.mNameTextView.setText(getNoteText()); - noteBlockHolder.mUrlTextView.setText(StringUtils.notNullStr(getUserUrl())); + noteBlockHolder.mUrlTextView.setText(NotificationUtils.getClickableTextForIdUrl( + getUrlIdObject(), + getUserUrl(), + getOnNoteBlockTextClickListener() + )); + if (hasImageMediaItem()) { noteBlockHolder.mAvatarImageView.setImageUrl(getNoteMediaItem().optString("url", ""), WordPress.imageLoader); } @@ -88,6 +94,7 @@ private class UserActionNoteBlockHolder { public UserActionNoteBlockHolder(View view) { mNameTextView = (WPTextView) view.findViewById(R.id.name); mUrlTextView = (WPTextView) view.findViewById(R.id.url); + mUrlTextView.setMovementMethod(new NoteBlockLinkMovementMethod()); mActionButton = (WPTextView) view.findViewById(R.id.action_button); mActionButton.setOnClickListener(new View.OnClickListener() { @Override @@ -125,7 +132,7 @@ public void onErrorResponse(VolleyError volleyError) { } } - public String getUserUrl() { + public JSONObject getUrlIdObject() { if (getNoteData() == null) return null; JSONArray idsArray = getNoteData().optJSONArray("ids"); @@ -134,7 +141,7 @@ public String getUserUrl() { try { JSONObject idObject = idsArray.getJSONObject(i); if (idObject.has("url")) { - return UrlUtils.removeUrlScheme(idObject.getString("url")); + return idObject; } } catch (JSONException e) { AppLog.i(AppLog.T.NOTIFS, "Unexpected object in notifications ids array."); @@ -145,6 +152,14 @@ public String getUserUrl() { return null; } + public String getUserUrl() { + if (getUrlIdObject() != null) { + return UrlUtils.removeUrlScheme(getUrlIdObject().optString("url", "")); + } + + return null; + } + // Show or hide action button private boolean hasAction() { if (getNoteData() == null) { From 0a2174619d4373afd8090251f3ffff9b56a158ba Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Thu, 12 Jun 2014 15:18:17 -0700 Subject: [PATCH 006/144] Fixed wonky fragment transactions. Need to fix landscape tablet layout still. Protip: Never have your fragments in layout XML. --- res/layout/notifications_activity.xml | 10 +--- .../NewNotificationsActivity.java | 59 ++++++------------- 2 files changed, 20 insertions(+), 49 deletions(-) diff --git a/res/layout/notifications_activity.xml b/res/layout/notifications_activity.xml index 8e7db7aed358..371aee687079 100644 --- a/res/layout/notifications_activity.xml +++ b/res/layout/notifications_activity.xml @@ -3,14 +3,8 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java index 9ae86037481c..8958aa02b9bd 100644 --- a/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java +++ b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java @@ -45,15 +45,12 @@ public class NewNotificationsActivity extends WPActionBarActivity 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 static final String FRAGMENT_TAG_LIST = "notes_list"; private NewNotificationsListFragment mNotesList; private boolean mDualPane; private int mSelectedNoteId; private boolean mHasPerformedInitialUpdate; - private BlogPairId mTmpSelectedComment; - private BlogPairId mTmpSelectedReaderPost; - private BlogPairId mSelectedComment; - private BlogPairId mSelectedReaderPost; @Override public void onCreate(Bundle savedInstanceState) { @@ -74,8 +71,16 @@ public void onCreate(Bundle savedInstanceState) { FragmentManager fm = getFragmentManager(); fm.addOnBackStackChangedListener(mOnBackStackChangedListener); - mNotesList = (NewNotificationsListFragment) fm.findFragmentById(R.id.fragment_notes_list); - mNotesList.setOnNoteClickListener(new NoteClickListener()); + + if (savedInstanceState == null) { + mNotesList = new NewNotificationsListFragment(); + mNotesList.setOnNoteClickListener(new NoteClickListener()); + FragmentTransaction fragmentTransaction = fm.beginTransaction(); + fragmentTransaction.add(R.id.layout_fragment_container, mNotesList, FRAGMENT_TAG_LIST); + fragmentTransaction.commitAllowingStateLoss(); + } else { + mNotesList = (NewNotificationsListFragment) fm.findFragmentByTag(FRAGMENT_TAG_LIST); + } GCMIntentService.clearNotificationsMap(); @@ -118,30 +123,10 @@ protected void onResume() { 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); + } else { + mMenuDrawer.setDrawerIndicatorEnabled(false); } } }; @@ -283,15 +268,7 @@ private void openNote(final Note note) { 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); - } - } + ft.addToBackStack(null); ft.commitAllowingStateLoss(); } @@ -300,7 +277,7 @@ public void showBlogPreviewForSiteId(long siteId) { FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); - ft.add(R.id.layout_fragment_container, readerPostListFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.replace(R.id.layout_fragment_container, readerPostListFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.addToBackStack(null); ft.commitAllowingStateLoss(); } @@ -310,7 +287,7 @@ public void showPostForSiteAndPostId(long siteId, long postId) { FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); - ft.add(R.id.layout_fragment_container, readerPostDetailFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.replace(R.id.layout_fragment_container, readerPostDetailFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.addToBackStack(null); ft.commitAllowingStateLoss(); } From 204b9093ee0517fd043fe622bbcc6c336593bf40 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Thu, 12 Jun 2014 15:46:47 -0700 Subject: [PATCH 007/144] Removed now-unused private members. --- .../ui/notifications/NewNotificationsActivity.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java index 8958aa02b9bd..bd1165899cbb 100644 --- a/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java +++ b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java @@ -19,7 +19,6 @@ import org.wordpress.android.GCMIntentService; import org.wordpress.android.R; -import org.wordpress.android.models.BlogPairId; import org.wordpress.android.models.Note; import org.wordpress.android.ui.WPActionBarActivity; import org.wordpress.android.ui.comments.CommentActions; @@ -312,12 +311,6 @@ public void onSaveInstanceState(Bundle outState) { } outState.putBoolean(KEY_INITIAL_UPDATE, mHasPerformedInitialUpdate); outState.putInt(NOTE_ID_EXTRA, mSelectedNoteId); - if (mSelectedReaderPost != null) { - outState.putSerializable(KEY_SELECTED_POST_ID, mSelectedReaderPost); - } - if (mSelectedComment != null) { - outState.putSerializable(KEY_SELECTED_COMMENT_ID, mSelectedComment); - } super.onSaveInstanceState(outState); } @@ -344,7 +337,6 @@ protected Dialog onCreateDialog(int id) { */ @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(); @@ -360,7 +352,6 @@ public void onPostClicked(Note note, int remoteBlogId, int postId) { */ @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(); From db0f3b33617e79d439f3b0dabca559d0b7236e85 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Thu, 12 Jun 2014 21:26:50 -0700 Subject: [PATCH 008/144] Tablet improvements and fixed blinking when loading detail fragment. --- ...cations.xml => notifications_activity.xml} | 3 +- res/layout/notifications_activity.xml | 8 +++ .../NewNotificationsActivity.java | 36 ++++++----- .../NotificationsDetailListFragment.java | 60 +++++++++++-------- .../ui/notifications/blocks/NoteBlock.java | 33 +++++----- 5 files changed, 85 insertions(+), 55 deletions(-) rename res/layout-sw600dp-land/{notifications.xml => notifications_activity.xml} (94%) diff --git a/res/layout-sw600dp-land/notifications.xml b/res/layout-sw600dp-land/notifications_activity.xml similarity index 94% rename from res/layout-sw600dp-land/notifications.xml rename to res/layout-sw600dp-land/notifications_activity.xml index 8a6dbcb872ff..39838ca37782 100644 --- a/res/layout-sw600dp-land/notifications.xml +++ b/res/layout-sw600dp-land/notifications_activity.xml @@ -6,6 +6,7 @@ diff --git a/res/layout/notifications_activity.xml b/res/layout/notifications_activity.xml index 371aee687079..37740a2c25eb 100644 --- a/res/layout/notifications_activity.xml +++ b/res/layout/notifications_activity.xml @@ -7,4 +7,12 @@ android:id="@+id/layout_fragment_container" android:layout_width="match_parent" android:layout_height="match_parent"> + + + + \ No newline at end of file diff --git a/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java index bd1165899cbb..50018c45d32f 100644 --- a/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java +++ b/src/org/wordpress/android/ui/notifications/NewNotificationsActivity.java @@ -47,6 +47,7 @@ public class NewNotificationsActivity extends WPActionBarActivity private static final String FRAGMENT_TAG_LIST = "notes_list"; private NewNotificationsListFragment mNotesList; + private Fragment mDetailFragment; private boolean mDualPane; private int mSelectedNoteId; private boolean mHasPerformedInitialUpdate; @@ -70,16 +71,8 @@ public void onCreate(Bundle savedInstanceState) { FragmentManager fm = getFragmentManager(); fm.addOnBackStackChangedListener(mOnBackStackChangedListener); - - if (savedInstanceState == null) { - mNotesList = new NewNotificationsListFragment(); - mNotesList.setOnNoteClickListener(new NoteClickListener()); - FragmentTransaction fragmentTransaction = fm.beginTransaction(); - fragmentTransaction.add(R.id.layout_fragment_container, mNotesList, FRAGMENT_TAG_LIST); - fragmentTransaction.commitAllowingStateLoss(); - } else { - mNotesList = (NewNotificationsListFragment) fm.findFragmentByTag(FRAGMENT_TAG_LIST); - } + mNotesList = (NewNotificationsListFragment) fm.findFragmentById(R.id.fragment_notes_list); + mNotesList.setOnNoteClickListener(new NoteClickListener()); GCMIntentService.clearNotificationsMap(); @@ -260,14 +253,23 @@ private void openNote(final Note note) { } // create detail fragment for this note type - Fragment detailFragment = getDetailFragmentForNote(note); + mDetailFragment = getDetailFragmentForNote(note); // swap the fragment FragmentTransaction ft = fm.beginTransaction(); - ft.replace(R.id.layout_fragment_container, detailFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.add(R.id.layout_fragment_container, mDetailFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATIONS_OPENED_NOTIFICATION_DETAILS); - ft.addToBackStack(null); + // 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); + } + } + ft.commitAllowingStateLoss(); } @@ -276,7 +278,10 @@ public void showBlogPreviewForSiteId(long siteId) { FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); - ft.replace(R.id.layout_fragment_container, readerPostListFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + if (mDetailFragment != null) { + ft.hide(mDetailFragment); + } + ft.add(R.id.layout_fragment_container, readerPostListFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.addToBackStack(null); ft.commitAllowingStateLoss(); } @@ -286,6 +291,9 @@ public void showPostForSiteAndPostId(long siteId, long postId) { FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); + if (mDetailFragment != null) { + ft.hide(mDetailFragment); + } ft.replace(R.id.layout_fragment_container, readerPostDetailFragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.addToBackStack(null); ft.commitAllowingStateLoss(); diff --git a/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java index 0eb5d84d8bab..5309d0aaba23 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java +++ b/src/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java @@ -5,6 +5,7 @@ import android.app.ListFragment; import android.content.Context; +import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -76,29 +77,7 @@ public void onActivityCreated(Bundle bundle) { } // Loop through the body items in this note, and create blocks for each. - // TODO asynctask this work - JSONArray bodyArray = mNote.getBody(); - 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 (NoteBlockIdType.fromString(noteBlockTypeString) == NoteBlockIdType.USER) { - noteBlock = new UserNoteBlock(noteObject, mOnNoteBlockTextClickListener, mOnSiteFollowListener); - } else { - noteBlock = new NoteBlock(noteObject, mOnNoteBlockTextClickListener); - } - - mNoteBlockArray.add(noteBlock); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - setListAdapter(new NoteBlockAdapter(getActivity(), mNoteBlockArray)); - } + new LoadNoteBlocksTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @Override @@ -117,7 +96,7 @@ private class NoteBlockAdapter extends ArrayAdapter { private LayoutInflater mLayoutInflater; NoteBlockAdapter(Context context, List noteBlocks) { - super(context, R.layout.menu_drawer_row, R.id.menu_row_title, noteBlocks); + super(context, 0, noteBlocks); mNoteBlockList = noteBlocks; mLayoutInflater = LayoutInflater.from(context); @@ -173,4 +152,37 @@ public void onSiteFollow(boolean success) { } }; + + private class LoadNoteBlocksTask extends AsyncTask { + + @Override + protected Void doInBackground(Void... params) { + JSONArray bodyArray = mNote.getBody(); + 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 (NoteBlockIdType.fromString(noteBlockTypeString) == NoteBlockIdType.USER) { + noteBlock = new UserNoteBlock(noteObject, mOnNoteBlockTextClickListener, mOnSiteFollowListener); + } else { + noteBlock = new NoteBlock(noteObject, mOnNoteBlockTextClickListener); + } + + mNoteBlockArray.add(noteBlock); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + setListAdapter(new NoteBlockAdapter(getActivity(), mNoteBlockArray)); + } + } } diff --git a/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java b/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java index 01ffae5da92c..c7c407ad0a47 100644 --- a/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java +++ b/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java @@ -95,7 +95,7 @@ public View configureView(View view) { if (hasImageMediaItem()) { noteBlockHolder.getImageView().setImageUrl(mMediaItem.optString("url", ""), WordPress.imageLoader); noteBlockHolder.getImageView().setVisibility(View.VISIBLE); - } else if (noteBlockHolder.getImageView() != null) { + } else if (noteBlockHolder.imageView != null) { noteBlockHolder.getImageView().setVisibility(View.GONE); } @@ -105,8 +105,8 @@ public View configureView(View view) { noteBlockHolder.getVideoView().setVisibility(View.VISIBLE); // Attach a mediaController if we are displaying a video. - final MediaController mediaController = new MediaController(noteBlockHolder.mVideoView.getContext()); - mediaController.setMediaPlayer(noteBlockHolder.mVideoView); + final MediaController mediaController = new MediaController(noteBlockHolder.getVideoView().getContext()); + mediaController.setMediaPlayer(noteBlockHolder.getVideoView()); noteBlockHolder.getVideoView().setMediaController(mediaController); mediaController.requestFocus(); @@ -118,7 +118,7 @@ public void onPrepared(MediaPlayer mp) { mediaController.show(0); } }); - } else if (noteBlockHolder.getVideoView() != null) { + } else if (noteBlockHolder.videoView != null) { noteBlockHolder.getVideoView().setVisibility(View.GONE); } @@ -138,10 +138,11 @@ public Object getViewHolder(View view) { } private static class BasicNoteBlockHolder { + public NetworkImageView imageView; + public VideoView videoView; + private final LinearLayout mRootLayout; private final WPTextView mTextView; - private NetworkImageView mImageView; - private VideoView mVideoView; BasicNoteBlockHolder(View view) { mRootLayout = (LinearLayout)view; @@ -154,30 +155,30 @@ public WPTextView getTextView() { } public NetworkImageView getImageView() { - if (mImageView == null) { - mImageView = new NetworkImageView(mRootLayout.getContext()); + if (imageView == null) { + imageView = new NetworkImageView(mRootLayout.getContext()); int imageSize = DisplayUtils.dpToPx(mRootLayout.getContext(), 220); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(imageSize, imageSize); layoutParams.gravity = Gravity.CENTER_HORIZONTAL; layoutParams.setMargins(0, 0, 0, DisplayUtils.dpToPx(mRootLayout.getContext(), 16)); - mImageView.setLayoutParams(layoutParams); - mRootLayout.addView(mImageView); + imageView.setLayoutParams(layoutParams); + mRootLayout.addView(imageView); } - return mImageView; + return imageView; } public VideoView getVideoView() { - if (mVideoView == null) { - mVideoView = new VideoView(mRootLayout.getContext()); + if (videoView == null) { + videoView = new VideoView(mRootLayout.getContext()); FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DisplayUtils.dpToPx(mRootLayout.getContext(), 220)); layoutParams.setMargins(0, 0, 0, DisplayUtils.dpToPx(mRootLayout.getContext(), 16)); - mVideoView.setLayoutParams(layoutParams); - mRootLayout.addView(mVideoView); + videoView.setLayoutParams(layoutParams); + mRootLayout.addView(videoView); } - return mVideoView; + return videoView; } } } From e2d5360dd7621f231c0be9dd6aae6e7092f46a08 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Thu, 12 Jun 2014 22:00:07 -0700 Subject: [PATCH 009/144] Code cleanup, moved media player attachment to where VideoView was being created. --- .../ui/notifications/blocks/NoteBlock.java | 76 +++++++++++-------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java b/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java index c7c407ad0a47..ba0d9edc223f 100644 --- a/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java +++ b/src/org/wordpress/android/ui/notifications/blocks/NoteBlock.java @@ -95,31 +95,16 @@ public View configureView(View view) { if (hasImageMediaItem()) { noteBlockHolder.getImageView().setImageUrl(mMediaItem.optString("url", ""), WordPress.imageLoader); noteBlockHolder.getImageView().setVisibility(View.VISIBLE); - } else if (noteBlockHolder.imageView != null) { - noteBlockHolder.getImageView().setVisibility(View.GONE); + } else { + noteBlockHolder.hideImageView(); } // Note video if (hasVideoMediaItem()) { noteBlockHolder.getVideoView().setVideoURI(Uri.parse(mMediaItem.optString("url", ""))); noteBlockHolder.getVideoView().setVisibility(View.VISIBLE); - - // Attach a mediaController if we are displaying a video. - final MediaController mediaController = new MediaController(noteBlockHolder.getVideoView().getContext()); - mediaController.setMediaPlayer(noteBlockHolder.getVideoView()); - - noteBlockHolder.getVideoView().setMediaController(mediaController); - mediaController.requestFocus(); - noteBlockHolder.getVideoView().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); - } - }); - } else if (noteBlockHolder.videoView != null) { - noteBlockHolder.getVideoView().setVisibility(View.GONE); + } else { + noteBlockHolder.hideVideoView(); } // Note text @@ -138,12 +123,12 @@ public Object getViewHolder(View view) { } private static class BasicNoteBlockHolder { - public NetworkImageView imageView; - public VideoView videoView; - private final LinearLayout mRootLayout; private final WPTextView mTextView; + private NetworkImageView mImageView; + private VideoView mVideoView; + BasicNoteBlockHolder(View view) { mRootLayout = (LinearLayout)view; mTextView = (WPTextView) view.findViewById(R.id.note_text); @@ -155,30 +140,57 @@ public WPTextView getTextView() { } public NetworkImageView getImageView() { - if (imageView == null) { - imageView = new NetworkImageView(mRootLayout.getContext()); + if (mImageView == null) { + mImageView = new NetworkImageView(mRootLayout.getContext()); int imageSize = DisplayUtils.dpToPx(mRootLayout.getContext(), 220); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(imageSize, imageSize); layoutParams.gravity = Gravity.CENTER_HORIZONTAL; layoutParams.setMargins(0, 0, 0, DisplayUtils.dpToPx(mRootLayout.getContext(), 16)); - imageView.setLayoutParams(layoutParams); - mRootLayout.addView(imageView); + mImageView.setLayoutParams(layoutParams); + mRootLayout.addView(mImageView); } - return imageView; + return mImageView; } public VideoView getVideoView() { - if (videoView == null) { - videoView = new VideoView(mRootLayout.getContext()); + if (mVideoView == null) { + mVideoView = new VideoView(mRootLayout.getContext()); FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DisplayUtils.dpToPx(mRootLayout.getContext(), 220)); layoutParams.setMargins(0, 0, 0, DisplayUtils.dpToPx(mRootLayout.getContext(), 16)); - videoView.setLayoutParams(layoutParams); - mRootLayout.addView(videoView); + mVideoView.setLayoutParams(layoutParams); + mRootLayout.addView(mVideoView); + + // 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 videoView; + return mVideoView; + } + + public void hideImageView() { + if (mImageView != null) { + mImageView.setVisibility(View.GONE); + } + } + + public void hideVideoView() { + if (mVideoView != null) { + mVideoView.setVisibility(View.GONE); + } } } } From 8ac0b6da9dc47d21e24690411a5e988d5a58e4e5 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Fri, 13 Jun 2014 13:54:03 -0700 Subject: [PATCH 010/144] Fixed row highlight on tablets. --- res/drawable/list_bg_selector.xml | 6 ++-- res/drawable/reader_list_selector.xml | 5 ++++ res/layout/empty_listview.xml | 2 +- res/layout/notifications_list_item.xml | 2 +- res/layout/post_list_row.xml | 2 +- res/layout/post_listview.xml | 3 +- res/values/styles.xml | 4 --- .../NewNotificationsListFragment.java | 30 ------------------- 8 files changed, 13 insertions(+), 41 deletions(-) diff --git a/res/drawable/list_bg_selector.xml b/res/drawable/list_bg_selector.xml index 71293361c343..1d07bd47d572 100644 --- a/res/drawable/list_bg_selector.xml +++ b/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/res/drawable/reader_list_selector.xml b/res/drawable/reader_list_selector.xml index 5914df34a735..e4d514b3696a 100644 --- a/res/drawable/reader_list_selector.xml +++ b/res/drawable/reader_list_selector.xml @@ -6,6 +6,11 @@ + + + + + diff --git a/res/layout/empty_listview.xml b/res/layout/empty_listview.xml index 7dfa355b3fb5..68628a885e64 100644 --- a/res/layout/empty_listview.xml +++ b/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" /> + android:background="@drawable/list_bg_selector"> + android:drawSelectorOnTop="false" + android:listSelector="@drawable/reader_list_selector"/> bold - - + From 97883a8aa7205305f197cf078beb6fd7816028a6 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Mon, 11 Aug 2014 16:23:27 -0700 Subject: [PATCH 052/144] Made the note block image slightly smaller. --- .../wordpress/android/ui/notifications/blocks/NoteBlock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 2ef2bb25314c..3a1e9ec2484c 100644 --- 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 @@ -169,7 +169,7 @@ public WPTextView getTextView() { public ImageView getImageView() { if (mImageView == null) { mImageView = new ImageView(mRootLayout.getContext()); - int imageSize = DisplayUtils.dpToPx(mRootLayout.getContext(), 220); + int imageSize = DisplayUtils.dpToPx(mRootLayout.getContext(), 180); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(imageSize, imageSize); layoutParams.gravity = Gravity.CENTER_HORIZONTAL; mImageView.setLayoutParams(layoutParams); From 55bb291d7f283142a36b6eab51a6653d4e1d3c29 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Tue, 12 Aug 2014 16:07:59 -0700 Subject: [PATCH 053/144] Design updates to notifications list. --- .../org/wordpress/android/models/Note.java | 27 +++ .../NotificationsDetailListFragment.java | 3 - .../ui/notifications/TestNotesAdapter.java | 46 ++++- .../wordpress/android/util/DateTimeUtils.java | 28 +++ .../res/drawable/list_unread_bg_selector.xml | 19 ++ .../res/layout/notifications_list_item.xml | 164 +++++++++++------- WordPress/src/main/res/values/colors.xml | 4 +- WordPress/src/main/res/values/dimens.xml | 2 +- WordPress/src/main/res/values/strings.xml | 2 + .../src/main/res/values/styles_calypso.xml | 7 +- 10 files changed, 220 insertions(+), 82 deletions(-) create mode 100644 WordPress/src/main/res/drawable/list_unread_bg_selector.xml diff --git a/WordPress/src/main/java/org/wordpress/android/models/Note.java b/WordPress/src/main/java/org/wordpress/android/models/Note.java index 5b18421eba89..562cb9cb159e 100644 --- a/WordPress/src/main/java/org/wordpress/android/models/Note.java +++ b/WordPress/src/main/java/org/wordpress/android/models/Note.java @@ -5,6 +5,7 @@ import android.text.Spannable; import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.Log; import com.simperium.client.BucketSchema; @@ -52,6 +53,13 @@ public static enum EnabledActions { ACTION_LIKE } + public static enum NoteTimeGroup { + GROUP_TODAY, + GROUP_YESTERDAY, + GROUP_LAST_WEEK, + GROUP_OLDER + } + private JSONObject mActions; private JSONObject mNoteJSON; @@ -62,6 +70,8 @@ public static enum EnabledActions { private Spannable mFormattedSubject; private transient String mIconUrl; private transient String mNoteType; + private NoteTimeGroup mTimeGroup; + /** * Create a note using JSON from Simperium @@ -170,6 +180,23 @@ public String getCommentPreview() { return mCommentPreview; } + public NoteTimeGroup getTimeGroup() { + if (mTimeGroup == null) { + long timestamp = getTimestamp() * 1000; + if (DateUtils.isToday(timestamp)) { + mTimeGroup = NoteTimeGroup.GROUP_TODAY; + } else if (DateTimeUtils.isYesterday(timestamp)) { + mTimeGroup = NoteTimeGroup.GROUP_YESTERDAY; + } else if (DateTimeUtils.isWithinSameWeek(timestamp)) { + mTimeGroup = NoteTimeGroup.GROUP_LAST_WEEK; + } else { + mTimeGroup = NoteTimeGroup.GROUP_OLDER; + } + } + + return mTimeGroup; + } + /** * The inverse of isRead */ 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 index 58034860f933..b669331df3c5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java @@ -149,7 +149,6 @@ protected Void doInBackground(Void... params) { 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; @@ -170,8 +169,6 @@ protected Void doInBackground(Void... params) { noteBlock = new NoteBlock(noteObject, mOnNoteBlockTextClickListener); } - - mNoteBlockArray.add(noteBlock); } catch (JSONException e) { e.printStackTrace(); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/TestNotesAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/TestNotesAdapter.java index 425ac9aa58a5..2150495d905c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/TestNotesAdapter.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/TestNotesAdapter.java @@ -15,7 +15,6 @@ import org.wordpress.android.R; import org.wordpress.android.models.Note; -import org.wordpress.android.util.DateTimeUtils; import org.wordpress.android.util.PhotonUtils; import org.wordpress.android.widgets.NoticonTextView; import org.wordpress.android.widgets.WPNetworkImageView; @@ -26,6 +25,8 @@ class TestNotesAdapter extends CursorAdapter { private final Query mQuery; private int mSelectedPosition = ListView.INVALID_POSITION; private boolean mShouldHighlightRows; + private int mReadBackgroundResId; + private int mUnreadBackgroundResId; TestNotesAdapter(Context context, Bucket bucket) { super(context, null, 0x0); @@ -34,6 +35,8 @@ class TestNotesAdapter extends CursorAdapter { mQuery = bucket.query().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() { @@ -85,6 +88,30 @@ public void bindView(View view, Context context, Cursor cursor) { NoteViewHolder noteViewHolder = (NoteViewHolder) view.getTag(); + // TODO This is probably bad for scrolling performance, optimize + // Get the previous note to compare timestamps for header display + Note previousNote = null; + if (bucketCursor.moveToPrevious()) { + previousNote = bucketCursor.getObject(); + bucketCursor.moveToNext(); + } + + if (previousNote != null && previousNote.getTimeGroup() == note.getTimeGroup()) { + noteViewHolder.headerView.setVisibility(View.GONE); + } else { + if (note.getTimeGroup() == Note.NoteTimeGroup.GROUP_TODAY) { + noteViewHolder.headerText.setText(context.getString(R.string.stats_timeframe_today).toUpperCase()); + } else if (note.getTimeGroup() == Note.NoteTimeGroup.GROUP_YESTERDAY) { + noteViewHolder.headerText.setText(context.getString(R.string.stats_timeframe_yesterday).toUpperCase()); + } else if (note.getTimeGroup() == Note.NoteTimeGroup.GROUP_LAST_WEEK) { + noteViewHolder.headerText.setText(context.getString(R.string.last_week).toUpperCase()); + } else { + noteViewHolder.headerText.setText(context.getString(R.string.older).toUpperCase()); + } + + noteViewHolder.headerView.setVisibility(View.VISIBLE); + } + noteViewHolder.txtLabel.setText(note.getFormattedSubject()); if (note.isCommentType()) { noteViewHolder.txtLabel.setMaxLines(2); @@ -95,8 +122,6 @@ public void bindView(View view, Context context, Cursor cursor) { noteViewHolder.txtDetail.setVisibility(View.GONE); } - noteViewHolder.txtTime.setText(DateTimeUtils.timestampToTimeSpan(note.getTimestamp())); - String avatarUrl = PhotonUtils.fixAvatar(note.getIconURL(), mAvatarSz); noteViewHolder.imgAvatar.setImageUrl(avatarUrl, WPNetworkImageView.ImageType.AVATAR); @@ -112,7 +137,11 @@ public void bindView(View view, Context context, Cursor cursor) { noteViewHolder.noteIcon.setVisibility(View.GONE); } - noteViewHolder.unreadIndicator.setVisibility(note.isUnread() ? View.VISIBLE : View.INVISIBLE); + if (note.isUnread()) { + view.setBackgroundResource(mUnreadBackgroundResId); + } else { + view.setBackgroundResource(mReadBackgroundResId); + } } public void setShouldHighlightRows(boolean shouldHighlightRows) { @@ -120,18 +149,19 @@ public void setShouldHighlightRows(boolean shouldHighlightRows) { } private static class NoteViewHolder { + private final View headerView; + private final TextView headerText; + private final TextView txtLabel; private final TextView txtDetail; - private final TextView txtTime; - private final View unreadIndicator; private final WPNetworkImageView imgAvatar; private final NoticonTextView noteIcon; NoteViewHolder(View view) { + headerView = view.findViewById(R.id.time_header); + headerText = (TextView)view.findViewById(R.id.header_date_text); txtLabel = (TextView) view.findViewById(R.id.note_label); txtDetail = (TextView) view.findViewById(R.id.note_detail); - txtTime = (TextView) view.findViewById(R.id.note_time); - unreadIndicator = view.findViewById(R.id.unread_indicator); imgAvatar = (WPNetworkImageView) view.findViewById(R.id.note_avatar); noteIcon = (NoticonTextView) view.findViewById(R.id.note_icon); } diff --git a/WordPress/src/main/java/org/wordpress/android/util/DateTimeUtils.java b/WordPress/src/main/java/org/wordpress/android/util/DateTimeUtils.java index ef451974ccdf..0aba3da24d5c 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/DateTimeUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/util/DateTimeUtils.java @@ -1,6 +1,7 @@ package org.wordpress.android.util; import android.text.format.DateUtils; +import android.text.format.Time; import org.wordpress.android.R; import org.wordpress.android.WordPress; @@ -145,4 +146,31 @@ public static String timestampToTimeSpan(long timeStamp) { Date dtGmt = timestampToDate(timeStamp); return javaDateToTimeSpan(dtGmt); } + + /** + * @return true if the supplied when is yesterday else false + * Based on isToday from DateUtils class + */ + public static boolean isYesterday(long when) { + Time time = new Time(); + time.set(when); + + int thenYear = time.year; + int thenMonth = time.month; + int thenMonthDay = time.monthDay; + + time.set(System.currentTimeMillis()); + return (thenYear == time.year) + && (thenMonth == time.month) + && (thenMonthDay == time.monthDay - 1); + } + + /** + * @return true if the supplied when is within 7 days of now + */ + public static boolean isWithinSameWeek(long when) { + long oneWeekMillis = 60 * 60 * 24 * 7 * 1000; + + return System.currentTimeMillis() - when <= oneWeekMillis; + } } 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/layout/notifications_list_item.xml b/WordPress/src/main/res/layout/notifications_list_item.xml index f85e50a75a78..93329e3ab916 100644 --- a/WordPress/src/main/res/layout/notifications_list_item.xml +++ b/WordPress/src/main/res/layout/notifications_list_item.xml @@ -3,92 +3,122 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@drawable/list_bg_selector" - android:orientation="horizontal" - android:paddingBottom="@dimen/margin_medium" - android:paddingRight="@dimen/margin_large" - android:paddingTop="@dimen/margin_medium"> - - - - + + - - - - - - + android:orientation="vertical" + android:background="@color/white" + android:paddingRight="@dimen/margin_extra_large" + android:paddingLeft="@dimen/margin_extra_large" + android:clickable="false" + android:visibility="gone" + tools:visibility="visible"> + + + + + + + + + + + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:paddingBottom="@dimen/margin_medium" + android:paddingRight="@dimen/margin_extra_large" + android:paddingLeft="@dimen/margin_extra_large" + android:paddingTop="@dimen/margin_medium"> - + + + + + + + + + + - - + android:layout_marginTop="-1dp" + android:ellipsize="end" + android:maxLines="2" + android:textColor="@color/calypso_blue" + android:textSize="@dimen/text_sz_medium" + android:visibility="gone" + tools:text="What an amazing post!" + tools:visibility="visible" /> + - + \ 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 35fcb2d8f838..78b5df8e95ea 100644 --- a/WordPress/src/main/res/values/colors.xml +++ b/WordPress/src/main/res/values/colors.xml @@ -107,7 +107,7 @@ #00aadc #324155 - #b2c6d3 + #aac0cf #90aec2 - #e8f0f5 + #f4f8fa diff --git a/WordPress/src/main/res/values/dimens.xml b/WordPress/src/main/res/values/dimens.xml index 2ac8d6fcab28..7849911df947 100644 --- a/WordPress/src/main/res/values/dimens.xml +++ b/WordPress/src/main/res/values/dimens.xml @@ -99,7 +99,7 @@ 600dp - 28dp + 21dp 3dp 8dp diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index a07ff8067e83..ed48beab77fe 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -469,6 +469,8 @@ Follow or unfollow this blog Reply failed No notifications + Last week + Older Reader diff --git a/WordPress/src/main/res/values/styles_calypso.xml b/WordPress/src/main/res/values/styles_calypso.xml index 3a15c206bc0a..384561a3c2cb 100644 --- a/WordPress/src/main/res/values/styles_calypso.xml +++ b/WordPress/src/main/res/values/styles_calypso.xml @@ -3,8 +3,13 @@ Calysp (WP.com homepage) styles --> - + From ba0bc91761e204241af709fe763be54b7d697459 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Fri, 15 Aug 2014 10:36:34 -0700 Subject: [PATCH 054/144] `CommentDetailFragment` design updates: * Action toolbar is now shown at bottom of comment * 'More' button shown that will show a PopupMenu when tapped with additional options * Color/theme updates. Added calypso theme. * New comment moderation button icons. --- WordPress/src/main/AndroidManifest.xml | 6 +- .../ui/comments/CommentDetailFragment.java | 133 ++++++++++++++---- .../NotificationsDetailListFragment.java | 17 ++- .../ic_comment_moderate_like.png | Bin 0 -> 2175 bytes .../ic_comment_moderate_like_active.png | Bin 0 -> 2162 bytes .../ic_comment_moderate_more.png | Bin 0 -> 1827 bytes .../ic_comment_moderate_spam.png | Bin 0 -> 1917 bytes .../ic_comment_moderate_trash.png | Bin 0 -> 2056 bytes .../ic_comment_moderate_like.png | Bin 0 -> 1763 bytes .../ic_comment_moderate_like_active.png | Bin 0 -> 1720 bytes .../ic_comment_moderate_more.png | Bin 0 -> 1578 bytes .../ic_comment_moderate_spam.png | Bin 0 -> 1685 bytes .../ic_comment_moderate_trash.png | Bin 0 -> 1752 bytes .../ic_comment_moderate_like.png | Bin 0 -> 2538 bytes .../ic_comment_moderate_like_active.png | Bin 0 -> 2491 bytes .../ic_comment_moderate_more.png | Bin 0 -> 2092 bytes .../ic_comment_moderate_spam.png | Bin 0 -> 2097 bytes .../ic_comment_moderate_trash.png | Bin 0 -> 2249 bytes .../ic_comment_moderate_like.png | Bin 0 -> 3390 bytes .../ic_comment_moderate_like_active.png | Bin 0 -> 3442 bytes .../ic_comment_moderate_more.png | Bin 0 -> 2791 bytes .../ic_comment_moderate_spam.png | Bin 0 -> 2609 bytes .../ic_comment_moderate_trash.png | Bin 0 -> 2606 bytes .../res/drawable/moderate_button_selector.xml | 6 +- .../main/res/layout/comment_action_footer.xml | 82 +++++++++++ .../res/layout/comment_detail_fragment.xml | 110 ++++----------- .../src/main/res/layout/note_block_basic.xml | 3 +- .../layout/note_block_formatted_comment.xml | 3 +- .../src/main/res/layout/note_block_user.xml | 2 +- .../notifications_fragment_detail_list.xml | 2 - .../res/layout/notifications_list_item.xml | 4 +- WordPress/src/main/res/values/colors.xml | 2 +- WordPress/src/main/res/values/strings.xml | 1 + WordPress/src/main/res/values/styles.xml | 7 +- .../src/main/res/values/styles_calypso.xml | 4 + 35 files changed, 255 insertions(+), 127 deletions(-) create mode 100644 WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_like.png create mode 100644 WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_like_active.png create mode 100644 WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_more.png create mode 100644 WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_spam.png create mode 100644 WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_trash.png create mode 100644 WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_like.png create mode 100644 WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_like_active.png create mode 100644 WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_more.png create mode 100644 WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_spam.png create mode 100644 WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_trash.png create mode 100644 WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_like.png create mode 100644 WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_like_active.png create mode 100644 WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_more.png create mode 100644 WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_spam.png create mode 100644 WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_trash.png create mode 100644 WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_like.png create mode 100644 WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_like_active.png create mode 100644 WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_more.png create mode 100644 WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_spam.png create mode 100644 WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_trash.png create mode 100644 WordPress/src/main/res/layout/comment_action_footer.xml diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index 948ccff6d12b..a3bba44f0b79 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -83,7 +83,8 @@ + android:name=".ui.comments.CommentsActivity" + android:theme="@style/CalypsoTheme" /> @@ -240,7 +241,8 @@ + android:configChanges="orientation|keyboardHidden|screenSize" + android:theme="@style/CalypsoTheme" /> 0) { + popup.show(); + } + } + }; + void setComment(int localBlogId, long commentId) { setComment(localBlogId, CommentTable.getComment(localBlogId, commentId)); } @@ -352,8 +403,9 @@ private void showComment() { layoutBottom.setVisibility(View.GONE); // if a notification was passed, request its associated comment - if (mNote != null && !mIsRequestingComment) + if (mNote != null && !mIsRequestingComment) { showCommentForNote(mNote); + } return; } @@ -361,6 +413,12 @@ private void showComment() { scrollView.setVisibility(View.VISIBLE); layoutBottom.setVisibility(View.VISIBLE); + // Add action buttons footer + if (mNote == null && 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); @@ -577,10 +635,6 @@ private void moderateComment(final CommentStatus newStatus) { // disable buttons during request mLayoutButtons.setEnabled(false); - // animate the buttons out (updateStatusViews will re-display them when request completes) - mLayoutButtons.clearAnimation(); - AniUtils.flyOut(mLayoutButtons); - // hide status (updateStatusViews will un-hide it) if (mTxtStatus.getVisibility() == View.VISIBLE) { mTxtStatus.clearAnimation(); @@ -645,10 +699,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) { @@ -738,7 +788,10 @@ private void updateStatusViews() { return; } + int commentActionCount = 0; + if (mNote != null && canLike()) { + commentActionCount++; mBtnLikeComment.setVisibility(View.VISIBLE); toggleLikeButton(mNote.hasLikedComment()); @@ -760,6 +813,7 @@ private void updateStatusViews() { } if (canModerate()) { + commentActionCount++; setTextDrawable(mBtnModerateComment, moderationDrawResId); mBtnModerateComment.setText(moderationTextResId); mBtnModerateComment.setOnClickListener(new View.OnClickListener() { @@ -774,6 +828,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); @@ -784,14 +839,43 @@ 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); + } + + if (canEdit()) { + commentActionCount++; + mBtnEditComment.setVisibility(View.VISIBLE); + } else { + mBtnEditComment.setVisibility(View.GONE); + } + + + if (commentActionCount > 3) { + // Configure buttons for overflow menu + if (commentActionCount >= 4 && canEdit()) { + mBtnEditComment.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() || canLike())) { - AniUtils.flyIn(mLayoutButtons); + if (commentActionCount == 5) { + if (mComment.getStatusEnum() == CommentStatus.APPROVED && canModerate()) { + // Already approved layout: [Like][Spam][Trash][More] + mBtnModerateComment.setVisibility(View.GONE); + } else if (mComment.getStatusEnum() == CommentStatus.UNAPPROVED && canMarkAsSpam()) { + // Unmoderated layout: [Like][Approve][Spam][More] + mBtnSpamComment.setVisibility(View.GONE); + } + } + + mBtnMore.setVisibility(View.VISIBLE); + } else { + mBtnMore.setVisibility(View.GONE); } + + mLayoutButtons.setVisibility(View.VISIBLE); } /* @@ -841,6 +925,7 @@ private void showCommentForNote(Note note) { FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); NotificationsDetailListFragment fragment = NotificationsDetailListFragment.newInstance(note); + fragment.setFooterView(mLayoutButtons); fragmentTransaction.add(R.id.comment_content_container, fragment); fragmentTransaction.commitAllowingStateLoss(); @@ -944,12 +1029,12 @@ public void onErrorResponse(VolleyError error) { private void toggleLikeButton(boolean isLiked) { if (isLiked) { mBtnLikeComment.setText(getResources().getString(R.string.mnu_comment_liked)); - mBtnLikeComment.setSelected(true); - setTextDrawable(mBtnLikeComment, R.drawable.ic_cab_like_active); + mBtnLikeComment.setTextColor(getResources().getColor(R.color.orange_medium)); + setTextDrawable(mBtnLikeComment, R.drawable.ic_comment_moderate_like_active); } else { mBtnLikeComment.setText(getResources().getString(R.string.reader_label_like)); - mBtnLikeComment.setSelected(false); - setTextDrawable(mBtnLikeComment, R.drawable.ic_cab_like); + mBtnLikeComment.setTextColor(getResources().getColor(R.color.calypso_blue)); + setTextDrawable(mBtnLikeComment, R.drawable.ic_comment_moderate_like); } } } 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 index b669331df3c5..e16f7e55b230 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailListFragment.java @@ -32,6 +32,7 @@ public class NotificationsDetailListFragment extends ListFragment implements NotificationFragment { private Note mNote; private List mNoteBlockArray = new ArrayList(); + private ViewGroup mFooterView; public NotificationsDetailListFragment() { } @@ -51,10 +52,14 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa public void onActivityCreated(Bundle bundle) { super.onActivityCreated(bundle); - ListView list = getListView(); - list.setDivider(null); - list.setDividerHeight(0); - list.setHeaderDividersEnabled(false); + ListView listView = getListView(); + listView.setDivider(null); + listView.setDividerHeight(0); + listView.setHeaderDividersEnabled(false); + + if (mFooterView != null) { + listView.addFooterView(mFooterView); + } if (mNote == null) { return; @@ -77,6 +82,10 @@ public void reloadNoteBlocks() { new LoadNoteBlocksTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + public void setFooterView(ViewGroup footerView) { + mFooterView = footerView; + } + private class NoteBlockAdapter extends ArrayAdapter { private List mNoteBlockList; diff --git a/WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_like.png b/WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_like.png new file mode 100644 index 0000000000000000000000000000000000000000..99e79d4c50f4b3a556f8c2bc12a3975556ba9fda GIT binary patch literal 2175 zcmbVOX;2es8g34iLl8yA6;PW9Drm?7IU$6Qa5*4H7-1O3AtWsckb@*Zh$uJe040UT zCVha&xaBCs;S$7?Sx`W9KwY`y7RBwTIQzqzt=g{c{=V<+_j%sCs*ii_bJo!^ z)B*rN$JK@54XHM;g_q4zM?Wc3cu*R}5<>uuBMgE-S3WBk@`hNP=$O;lhNGS-|(@!DrMegojhy{|*iGovxlCeZ6j3pYy=PTp-s4bCtLw~vP zRc(n+vqGo=sQjwfs z|7)V^kIua;Re=Vv%}u?^QH!J6(K#0wRMxTwlZx6AlGZ*KG#BWSwl`)vz2)rL1@g8u zMw(T4YEz2Q3`%k7@u8*0s`(R*iP_lug8C-^tQph!c$-St%s;=z z_0-}&0Uy8uFo=6c*E4hXZ`*$w8AokCQ6#%5gmvMkRh=8a-R*rkczp9MCM5vz z#48e@;qCOk)!dT5=VSwV)WQbvIGb~d7VM?5ejH7>bV@%C?C_5@C0{miO~`i%+MQ+F z)yk|aQ<-$GrrDh<-M)dc_e$-W?0$VnZAe$ltc$&@(qc5S6Z)R?4&c!q@Jqrsz3+Kz zlWM<<-^x^*{9_%ZX|>T7H%^jnQV)B=#E3^RiG1CoX20MzBKL@Qwq+gJYd55eM#Y(S z$}8f`O+u4~aSb*x%W9zJH_aN@Oy-|k*AHk1UOeA;yH~&X?e7dkpz*E%b!eFOA9r5&CI_-3lRWqc2eJ)7* z!(!%6+0>)=P3c9uaCetxQq=davEkJAW!aH<)HmjIW@h2Wir(y+F~572C9iXn3vGVi zC~A@%M0z3I}BN|M&$amu+OC2k=vYz z3An}0G%IMSCenV+PPE82^)XaU${FSse2`PKse0y5b<6eJ-73#{M(3G1-i>Q%@Dn4S z>xNrB-&(d_XJFbfznK2&w{KTxxM=IpEHMtFR~tHRDo%&8+NNRBj5+GBB5FCnziH#?0Z-MCrc(hG%cIj*jOpyd-3LMwd0QKo z8VMyKvyKazdhu>|RjL`an}3_09CujVy#9v+@`kP$zG7lQ#Jm>X&gn_+&V6i{bE{U)!_JIO&8&RY;M%_6l)ABBbjTwG=~~C^3kF&BN5-F*`W9YQ z#0K8E<(;4-fB9>^itOg&WIv0G!h+((?uVnmzXZ9yHb*N;BUy^U=*#!Lso4gOLHv{T ziq>VC*X*&(mY5Hn_trHIOg9{@$mpApP<9XWJb33(wMqYF+V^8sg|`Cy+YIwiC1*p^ zW4Cau)O2FCxmUx|4;Wcw8B*3>d_yhMvR{>_>e)9{1*idYABx7y4;9WSe+gV2_c5+J H1Sb3kY(#x9 literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_like_active.png b/WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_like_active.png new file mode 100644 index 0000000000000000000000000000000000000000..30a3dd6a3d9ace2bacca42a61a03a686f5312e07 GIT binary patch literal 2162 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 literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_spam.png b/WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_spam.png new file mode 100644 index 0000000000000000000000000000000000000000..08741a28a2cf22c84d1da3e7b57e81381bc33309 GIT binary patch literal 1917 zcmbVNX;2eq7~XIQa;Ve`M05#N6iBkWBrypD41}{p5Ky8xNRGv%kZepA5^1Y|7os3) zRiNN~TNMvbp^8?bKrMnrX;Dxx0v?Eh;tA6Yi0u!@&U9yYzwdkZeV+H8T^BJY$ku9- z6#xLXA;J7eVug&4I#EyRU_FpdOMP(?-^s!pLI&;a1=t5b<2i6{<=(Is*vm)u=mK?dbgE;)uFfCMUk zR3;BjRijao^2~0>wq3PLZV4aCBVqATNj58^ts-IH-ara>+wZ z2?Y_LKc+@O2Gx@yfnXS9GN~|~!DPZ7AOgV%4I*wP1*UTtOb#*&{CJTGYiel%Cz2od z(H3#yl4Urq;?QVXt(L0wq+;qNG?>k18#xe!LTFGl%apiCM^S2Ah8g&%MxvIha5<&~ zjf^5OmW*@Bgr`FxC{zN$Ct{`MW1@(Z(R3me4W>dgg~Axuptc5&ME`N)v)Y>IWh#^w ziE6N9wS=fgg3B85Id{IN@qJQ4~=UACn|2P$eG1=aPvpRH|R62*hX6nSP$b z*bt=#7bzv^uwA*)?hrQMi&&1o8WrJ~IvT^0hC3iahT)h-hN(dRC?+^VAd<+HM!`&D zfrjGcqiT5yDh*U)3UDyP9Qo%wVE$L+Ls;qm$r_DthGuM!f3?f7i0DD%_EYl{hfm{! zDv6m<6T?y9v}%#jj!&N;qrR8zPzM$L>b% zdL10KEB(zz-_5k;Ck{?gd942#=mbEk9e?c}__JAc413!E^yw=W3B$QBdkQJmvVvO6 zxaH6KZqWTWey|FeI{;n zKe8q+{08N)eNf(5K-N{c&Cccfg8jYT3v%(&F%+km;t8bKTLt+mO^;S*HF+_56U7p* zi6u6Rb3ZS-V}-MNZ+BKtuN z#y7d+AlId^n_f0t(Y&o}x?R=yrbhF;;@*bkGT#!%lU2eMLBHwrK1YGdX*c)QSTW6c zeJ0Xf9);$o_8oHG;ki9MMX$|x)7R5>*f*(*SLu2^FhfWh1z(MBcP_2R$J`Sy^k$@u z_sYK@OOjY$$|`bGINkyDDK!%e%-dI6%}z90l#yQ_%RFmdto^mU{DHgU-prEuo%Urp ykNevu25;^5cs=!*FEa6xxr($1@M>H^8UgSZg;(y(whA%+5JLjy@Q?e&XZ#J15$Z+& literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_trash.png b/WordPress/src/main/res/drawable-hdpi/ic_comment_moderate_trash.png new file mode 100644 index 0000000000000000000000000000000000000000..bf91f68b44e73f3142e7fb0a4a8677909bb6c0c2 GIT binary patch literal 2056 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 literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_like_active.png b/WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_like_active.png new file mode 100644 index 0000000000000000000000000000000000000000..69c250f81c2cea546acd7bd50022ad83f59315f6 GIT binary patch literal 1720 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y-fM)1rW~NvsqBTl{gK*#8Vq82HtVM4(417gAxE|3FH`l)%q^j-u^*ptFL{sQxhmZytjNX4x; z)AK!o14Ry;uYI3ey!7zWx)lx=d{icy%uG0W%7fcU_%eTxeA5g5z!sK9H5Vrlk%#ST z7IfIDE}SA&o2il+wQ^?2E%yI8J7@VVKezw)vUi<;BBOvj+mQvQ<1;09h%Srw z_3^!_(8BaVx*_70=^7r+Y&M6wZK;=T{_{O>l0kqyL`GD_IryDd4QID~uR_i%54PVS zA}5@3?g=T>uD5HgSo)k%=n&)gtMZSPgzqtbGUBp!ZU{f~;bLYk&z)fXEfvO#8tQAW zb?~ZAJ+kIKnAMe!OocAbbNr?ZS+@@)_`aT|DUwb2> zYedn03*ew)_eiw86?)P)e8zvh5nfdEK zuR!FfQkvQ9agPy9fa$w)C&F3Y2uB~F6v6(~5sn7R$ye9)$CMyI8L(P5=vr{bKV5^UD zbJ-l@T)gNcmjXc}J-pqaIBat$R`h9@kc}qj^8+*lrOXWa8P>}wZ~^D$1ru`n&=CaY zohD?y&W73i7S6+Glt^4oNv54G@v=rIGV^UXB}f4RAEz*I&{rtPRM3PB@={WEAik^m_k})i+r4*SZNaizG$4hlfnoCsG?qr=?RQ9 z;JASx%m%G4IaN#2sRpef%<(*|S%>Rz+LDf2FdE0xv_`^+ry30w0;3IDGBr7jwF zbIx>0^udD_rugSXfDw9~8Bf>a*ned{gmwO(s-YklG}IpdYL~DI^k8WDtogy_+4yh* zm>CHSM~e2weh3=ju+nCG@X{X}FMFG3L5*nOi?8`h2Xuu+#d&6DZsWK(*TmS?@|9

q`R0rbP+<>QQB^W=d()m53k&#GPd$$DJS8<=S4pS0)xDywXyY#Q&L39uziL z#;y9ey!(_pb5F*I{I@(mKHPrz^Q49Pou)Oa6<_8XOnu`lp1ayDd#4^bGrp%o_ay$7 z{y6*lHRp$aotyZ#~(|7H?z0vS4MrEt0$*tcp$+d<*;PUo$-fOC+FLP7o{G1rs z(zlU|sdxk#s$1(4V;kyQwC7*VJvbT3*#LF6tCp{vd@9#b-?q=^_XTIKJF3cCvP^!DjEJy)kn{NJXz$kOcei$3oIj?u zu`An_QQ3Z$et+rQh&KOyb|H2eE;bvs)voVNJ9hg~)A5(uJk6brKfNDHfmB!WD`$>~g|@FA VrLV6|SQq+xThlV>y{QYz{szZHQ8xep literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_spam.png b/WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_spam.png new file mode 100644 index 0000000000000000000000000000000000000000..e91715328a63317b4eb5d0487b35d52ee0a9eba9 GIT binary patch literal 1685 zcmbVNeNfY87%%7{et@p%$rxx_#zc{}N!!?Ft|6e>1}X@F972kJIA zIK>a9o73|HywNgGZ>XFoaAOV;1wS@8Ii}zoj%g9UfSZa-fx7+SZFkEhdEfWR^ZPyD zm(LR7Kk%6nFonb6_-L1)iR=ov9&a!9ZL!&&vdh~{R0^}4G&1=ZjdN56G7|^1W-JR& z#4$s``et0g;Y`RjC8aPax;R8nngy6kM_@HmESkenL|7?IpMx_%CZ1&?l>CRa^?bl& zQ1Vwwbs`<5!Lvy7N4ZI<>=)Ge#BxxVMSO1GtOXu)x3_dAXX)Rlow(5F0qgg zjH)m>O8%HrDY^teL((`P6^O(2A`k>%SO7|-FbpmPAQ1=&MeGfSgAzmvBhVsX{Nl6L zXoC?+M5D)Tu_q-zn_(zKD9q2#7vzftB%LJ$<#M@;1A@X?jc`i=!C=;K!V>IeKyizn zHc^a;Bmfs9mPzI^N4cuqz1t#i5!xHYPn1UiBOpY zR*T(OEn#6WLXW%cnpnHzSn0oF5ek(V3!dX)_l}7QUgkT))^|AS zQpJM6?g*fKWYUcM*1#INQU(x;cYSAiBqkRH%sH}IA}MyYM*ZsKeZ2SO3Ngo*HGgqCtui<4 zmb%rrUE=@nBX0DO@FIX(e71H^^43!4fg2L%%$oFDg30NgRB3f-x_N5r(B0Mpmj>sS z2p)^$ihrD2_vb)+wX=Nd3neH$SjSB+lOA4ZeNxHnZ|a)<^>i=r*mggje=7Cmz~>E$ z-ZLwBDc9}KDzjq4hKfoSpoh}FO`pOI94f@FsYu_p^Cc&LUvWQG`=aU{Ke%EU&$D!G zbcV43U)Fi=itZA(yR6|WwFrVw_s8>~xD6TN)~YtAA!On+i{p8!`s%$0Cmj7<6WWd5 z)Asu^)w8>EudX49Q;Ju+*L2VJY%w+;!-C2pho!$Lir)Go&|^(rn2%Cif8s@MBky<| zl_hQ1x8=y{$9t{i_k${?IqhDN*J8YD_5{Q!_Q@j<*wVxFUVd~}HzKHUjP&^}*%pbG z#h$A;9m1KP|2W~5GW2I;$IFX5i)yJXb4TN<1fLdY55e=ix$~!I2L9l6LO*L#cy_(< ze1Xbwd2`3Mh??Nafg(r_Z=BVy>&dH1&3pLkg|;2l9|k*yy)Sz>KCxxCC9B(GBJA>u z)9y?av|Q->uCFVBfBKwr`YsVy(ZBIby4SYCPr2D4kIr{qw4-qk6un(etXn-%!Cks{ zMQ>QHa>4;9&Y4Eteox%k@qNR`ldGD#cxR(FsHYxm(WnFLYUZ&0?5o=Y_45W&3q3f| Wu~tb{(EH`CAB8q59zCwk*!&kug@3>R literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_trash.png b/WordPress/src/main/res/drawable-mdpi/ic_comment_moderate_trash.png new file mode 100644 index 0000000000000000000000000000000000000000..87d534326a6990d6e15eb128b2f9ef483f8a7291 GIT binary patch literal 1752 zcmbVNX;2eq7!H_nDUJsU6qRLFiq;$(LM{`O96*2|Ly${UmSjT~l5EUwAVICt;sG*( zMdd1pO06|Ot)*17QoIb zxx0PrMxjvL<>As;as}*kPQSA_9kb1?1i?W`Mx33Xv%S zN1RGj!~hb^gaASGK$?mPK>&wChuA?J4zvJZF(DR%N!}b9#1;f`1S~EvdV!=hlUgH) zm4=SmB2OYvOAtl@gOQz`P0tRbW2STl#OL$v94r=%)S%&30|8rT27LAygA~D4Ce%ov zm;tae!fBY95P_tpBO&OG3dK8O13sE4GGz=4Y-B)mCPS~c$2F{t6S2rYZoF3;kFy#P zMl6D3W|NAnhi3K|n9SXW4cQe*Zv;zBC|ML(C&g4|Jz^l_QV~dgp{r4~fXU*KS>&>r zTo#kVgCHJ@6~bc&1&Y}mshG#+jd8pW%L)+(LXuD!Bwh>-ldzal9-AW$ z9K*^DH~||}$e3M}v^$EGeh@2=m=Ku2OmP^d8|#1=Ekpzra34EH}GAHk~sPu3WuGYors{HtBYL}U-zx9^&tJiHqp z#6ZrBi5!kHbCZgkIVZU^B+hcDm)T1%8h1)$C%+;~Fkw zmax3cG62k}ZZGk?C%<>`xO7Q%_?1)R+Psd1vrkd?#24g(>zZO}XpQZc`z{t@5s?Qx z@)}*CtR_XarkmSe$;G<4rwgIRU5dp+x6V>uRINYkdGAT;;GT8#HQuT^kDTavN}sQ` zKB#wZSnb$WS4%zdr1rtwS75xbAx};grP6E z&GKu&RM6Q&Aym7_xFl(+@luvzE6*p>|P<;=@`A~QE&JCwuK!IeO~^Lswc=8 zJx)JP1}HWkQFq7H?+%g4N zY~%eDUpw=FXt4H3toNprYmFkqo-DHQ=p12V17PZjH#>-)EN0xw4(UC_CK z;_9WiK mf2%b?knjc@?4CL}x0RC0M-&TJ-KewwYvi(MX@fZR^S=QKD4Gxe literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_like.png b/WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_like.png new file mode 100644 index 0000000000000000000000000000000000000000..dba96740485bd72265421203d471f2b5e7b88555 GIT binary patch literal 2538 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 literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_more.png b/WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_more.png new file mode 100644 index 0000000000000000000000000000000000000000..10f426fbeebdf7a87530c0adc398f7ae1d45c033 GIT binary patch literal 2092 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 Y0;;%e)o~)&^iu%%8zZ^3oIm9M5Bo7fCIA2c literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_trash.png b/WordPress/src/main/res/drawable-xhdpi/ic_comment_moderate_trash.png new file mode 100644 index 0000000000000000000000000000000000000000..47198910a046c49fa2a2ae9de1ac249beb3b787d GIT binary patch literal 2249 zcmbVOdpK0<8eeX?>`F)Jgs}FIuroI^V`eminIZOW5Hi}wmNBz1n2VXw3@MT%myJW& zw(MLw5zTM^wzluM!%V)u}}*Cd_uM`xes+3Q(rz2En)_xF1*-}`;fI^yZR zZIzCp4gdhF+*}!6uyj;DS}WjpIWMvw76wvhKdHAcSSn+SA;5_%41z#6K05^Rg4o>X z$Xn1>08k6%`S?lwnA^!5As@+B+92h85sU_atqyV#n-dO6!5}DvC!ioE8=DXyk4r(Y z@JuvQM2A9ot}$ZBJI39I6BEuMaS;wwaI2gQ6YwD^8SwXWHO}87AX{mpfDs7Ny&l5+Q1ezl4yaHEw>R!%$6A# zkc1=ViKINC08}!vgM?913IdLFDFwcW$^1+#kbEi>Tr!lLEka?CXcV8X%0kG}~pYn?NFE3pmiSU!KzM64vQYv1Ga!VoQZ$AE7W}xdS{yg;Jp;R44-J z-UQH+$>#6`N`;kjftK=RKw{n=i0dL2^1+W4CiA`&0pb5;zJ%rem8zlOU{K2T_^)3`}9DZ(oc=CDsKmvHrh~dp)H8!6H0P1sY3@0D?Q_>f?kVlnkqgPU7#k^*m@bUSWgvFCoHz5S(KA!9eR-TiwX?hLN%49h97o4=eFJssSyZhPZyfV=vu zx8{Hrpc)T=N^zy1DYiLOYJ2h8z0)(h+A2-b)*wrfa~{oZ%qZO9rgYQfwC`F(e_fP9vY;ed~xx!&()6L#u)63mY-`6o(OH=}p1*ARi9`u%An{_PegQCY9 zj{Gvoi<{1SUFTt{ZG_JA-Hjg{Z1Ha_WGT<8hl*3_M__NLq}^%o<9BAgs}bbA>;WSWwqS~sj>ZWW$&^_ zG95bC02{Aw9_`ANL@QWXT36-SQ)M=WhWm%*ck;wT2`Y-%>5ko|R@g_lf3D2j{2lR$ ze(&5aykl#RPoJ73s*(G>j}?n**-*P$RC@qj6K7o;3yt)DNIjZhxmTTwSMau0i(WUy z#5SszTzQdUx*C-uBU=v#x{3cbWg;sO(>xyP}fT+Zak`MU1?;I;+(+p(4A2vg_ATK>rd!L z`Ja-QHM>m?(a|fRmaHg&748X ztFQJy81nY+upcm-I}=Ft3%l<)8!B~T8JTyL#!I3q^@B2{^7CA3^`EZc+1hCLcJ)7|X^E=h@`rL1U zV>=ep*KT{3vb$#^N+!j=MaIWCWts1#UPx4X(4h(}6c*fdu`jPEXl_s&s8E%M#Ga+= zxTm(~VkO>YdL5EsyGz7G?Y6>VimBxT4~9`&T^XMb?(2nqgENfi-|S z@V~{&)s6_cH;@zPANx%UbwH}KkhYdnGY0CnCD|v}z1`n)a6=DK|3+(+hP%a2rUy@W z{LA(x>8sY6{%>bbPV{YPo_%Ncd-|=Rf0*P|7A5#@sWAAVqtYWi+8Xd9Z+YiRU|&&^K? zhNFF2OWUTaUEhp0^a3=r(Tj9l4Mkg;z{|dJXZ^^o)b@!i4(6EY3kpu_P3|~V<9N=Z z{ok7~dGgW4pJ&fG@w9rguD&#|G1+ULI7FjHO@y*04p#Zi#NdZ~DdbC4M&<0TQ%{}> zAK_K~n6Ahd)7%Ox{nWVpwLfMq9WTmLW};^%15g7pf1nFJj*ZPLe=gjd-5FK1z=S^l DDHD?5 literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_like.png b/WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_like.png new file mode 100644 index 0000000000000000000000000000000000000000..ceb1b5dcefc7305920d95ffde9e47d896f646f85 GIT binary patch literal 3390 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 RNaz1(NOmr^#RR{&e*)mw%y$3) literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_like_active.png b/WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_like_active.png new file mode 100644 index 0000000000000000000000000000000000000000..999b2254f748dbe6562b575bcc5e788653f238a8 GIT binary patch literal 3442 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 I=a9rd0lS(m)&Kwi literal 0 HcmV?d00001 diff --git a/WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_trash.png b/WordPress/src/main/res/drawable-xxhdpi/ic_comment_moderate_trash.png new file mode 100644 index 0000000000000000000000000000000000000000..8c662f3bab72d73d0532ecb786e2c519b717e2de GIT binary patch literal 2606 zcmbVO2{csuAHSB;siD$lt!Y#qnw41@Vuma!A%wpgGuO<-%w=XYgZvW><^NuaEJKM3 zElMbrosx=(EJaybwCG7CEmZH0(*K=zI{nZ2-*fN%E%*EReD~kEd)?Nr)ts$28vp=J zCr73`JZ@D!vu48YhZ#}|Jm^a7eIy=GkVMK7fq*Rs3Ivf(0u~o^2U(nmup6Ki0I2c! zo<0&E*L5^DB*3tgG8ma46h;Gpm5nTv#SR7~$Uujh>OKjsZ=Egfq;f3(BcT8ge5}@#YWQ%Oi;`g@k1qi zNQhK2vI3!S2^|Gn`ppGFsH^J_VxjnZpx}^UWvoyv9)rUQ1WLcAq{R|<@V{pKR9fsA z5ej16K`|6AV#E0eGMWa%vHP_mr66n#ZG(spCxsQlgxKK%P$+R?(oygi42RF5;RqBs ziew8MnSdiv@OTP=U`w$en%h~Bn06Ek$~4DMv33M1g^DNR88`+WZ*M^*Q1NzD3WI=S zQY=Vz=F?awp;*EavcYM+e5KxRSkf=CG=>OdNg$CY1cgjjfEy2zKw=&giez|@kiWUI z*nFXK!$g^&Z~iht5q~?#aS%ZQ1<}xH(3JrxXA%)8C0{>nZDbdB~6FxmKr<+X9laCWOsr_1S@e#EkQs z9RCyRb2XFXexhpEtarI%x$cp>0s@XF$pee?c&Vs8XFd$XjoF`TNN~=}GffM;)u01a z;M+n{UarcbM~xKp-y5qlXx*$Y-oD}fj-5W*+ZE>hJ8BfKvJ_VkL_}Opj;UfB6Df6~fj=E!8JhFyT9P1O-Bse3IMx9>aVQ>eQ_$K`l=<(SG- z6TlYHbZYTfSRuR1bL^wsaq-N>j{uz@U(OC^o}Yxe#>y#Oj>*6m)*fQ zH}<3_ytij;TGFf)++&#zq|blC+YmAqcjNamRrKpp zgRFw~`(qtQt42f_M$?c7&0coqv?=yO>>p+hC!Q~J-jUqAr?*+2#ICH_K--bl~#Hkn0|wZ!G{B*t-#l>Hky;DD~_urJkg?Be}-$H$cBCmZJMKO?uP=BYLBD=2oVG=4G8se>K3YC%-=u+DT4L73IdWhWf*o&u`j4XP?|u zm8(A-y!-M&xngcry0a9KRM6i}tV(U^UK*Nml)sy+s2k6}p3U_pXZyPWIo*(Dchs=+`D_gDrkxEg&3Sy@O)3p6Umyx{s(I2KUax-hgQZ!0 zU0-7mc9tAiOpehvdITlq$qRyt79i@~BQG9X`6)FC?6`O3K9XJ>Rk6Z;_|b)R429#f{m6H_1nex;vS_WcMPr7x8~gS= z&~qKp(a{|)-)Y>TKDX(U%+KS4z8P>wrRw$7;gs0(hcARI2MmhC`Ok6;V|EP||0zv* zZrIh3g;1F571B5wKu9m5ESksGSLq#3R|k0OuCLGmi1JvyWNcT}8XQDp_$j%wJ=`4)OPP|4lsxaQoGH@K1r!c^mCC zE1}3H{W9I~f+zlXv2dR1QvVyy*t_Y>Tl<_CyvtV{bBWyKmvgw#HIM z{1@3Fr!>#Qg`1aJKYMhyopRh;cXVZU(Y77lM1*|xXx!k?;r25J2HMK5eM)IKQFOxi z$wY!SG}*VrQhhKdTfTpdZaJ<}%NoZG7&`L;ePCb^9qe75UI7+&02ztK$!DTpUmf9Z z?4zx8T<#dVOQCz5+%zvv$F#NhWKUvAcD&oW0|kCZxrq@j>0GIOhPPjSsYnx{4yov< z1Gtkf-Vi#C)J99RHb_;K$2RcKoJ<|!n+!gi)dE)1xrx`u1EzlAob1;#&)RK`{R;#8 BCKvz! literal 0 HcmV?d00001 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/layout/comment_action_footer.xml b/WordPress/src/main/res/layout/comment_action_footer.xml new file mode 100644 index 000000000000..57cf4c62375e --- /dev/null +++ b/WordPress/src/main/res/layout/comment_action_footer.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WordPress/src/main/res/layout/comment_detail_fragment.xml b/WordPress/src/main/res/layout/comment_detail_fragment.xml index 4bd11d423310..539e2aa03e83 100644 --- a/WordPress/src/main/res/layout/comment_detail_fragment.xml +++ b/WordPress/src/main/res/layout/comment_detail_fragment.xml @@ -7,15 +7,14 @@ + android:layout_height="match_parent"> + android:layout_above="@+id/layout_bottom" + android:fillViewport="true"> + 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" /> @@ -97,14 +96,13 @@ + android:textSize="@dimen/text_sz_large" + tools:text="text_content" /> @@ -116,60 +114,6 @@ android:layout_alignParentBottom="true" android:orientation="vertical"> - - - - - - - - - - - - - - + android:orientation="vertical" + android:background="@color/white"> + android:orientation="vertical" + android:background="@color/white"> + android:background="@color/white"> @@ -113,7 +113,7 @@ android:ellipsize="end" android:maxLines="2" android:textColor="@color/calypso_blue" - android:textSize="@dimen/text_sz_medium" + android:textSize="@dimen/text_sz_large" android:visibility="gone" tools:text="What an amazing post!" tools:visibility="visible" /> diff --git a/WordPress/src/main/res/values/colors.xml b/WordPress/src/main/res/values/colors.xml index 78b5df8e95ea..d78ce673bc45 100644 --- a/WordPress/src/main/res/values/colors.xml +++ b/WordPress/src/main/res/values/colors.xml @@ -109,5 +109,5 @@ #324155 #aac0cf #90aec2 - #f4f8fa + #e8f0f7 diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index ed48beab77fe..18a2a9116e7e 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -471,6 +471,7 @@ No notifications Last week Older + More Reader diff --git a/WordPress/src/main/res/values/styles.xml b/WordPress/src/main/res/values/styles.xml index 29e1da383bb2..08932d57a3d0 100644 --- a/WordPress/src/main/res/values/styles.xml +++ b/WordPress/src/main/res/values/styles.xml @@ -136,7 +136,7 @@ + + + + + + + + - - From 3f4e26c8c0e96a6bd6b27e5c604fce5a5328f1f5 Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Sat, 6 Sep 2014 12:32:36 -0700 Subject: [PATCH 126/144] List view position is now saved/restored. --- .../notifications/NotificationsActivity.java | 17 +++++- .../NotificationsListFragment.java | 57 ++++++++++++++----- 2 files changed, 58 insertions(+), 16 deletions(-) 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 84a92add95c7..c79ac4e018b3 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 @@ -11,6 +11,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.widget.ListView; import com.simperium.client.Bucket; import com.simperium.client.BucketObjectMissingException; @@ -44,6 +45,8 @@ public class NotificationsActivity extends WPActionBarActivity 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"; @@ -91,6 +94,12 @@ public void onCreate(Bundle savedInstanceState) { // 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(); } @@ -292,7 +301,8 @@ private void openNote(final Note note) { FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); - ft.replace(R.id.layout_fragment_container, mDetailFragment); + ft.hide(mNotesListFragment); + ft.add(R.id.layout_fragment_container, mDetailFragment); mMenuDrawer.setDrawerIndicatorEnabled(false); ft.addToBackStack(null); ft.commitAllowingStateLoss(); @@ -372,6 +382,11 @@ public void onSaveInstanceState(@Nonnull Bundle outState) { outState.putString(KEY_REPLY_TEXT, getCommentReplyText()); } + // Save list view scroll position + if (mNotesListFragment != null) { + outState.putInt(KEY_LIST_POSITION, mNotesListFragment.getScrollPosition()); + } + super.onSaveInstanceState(outState); } 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 c56723d41c89..cc0a1f3f6230 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 @@ -32,8 +32,10 @@ public class NotificationsListFragment extends ListFragment implements Bucket.Li private PullToRefreshHelper mFauxPullToRefreshHelper; private NotesAdapter mNotesAdapter; private OnNoteClickListener mNoteClickListener; + private boolean mShouldLoadFirstNote; private String mSelectedNoteId; + private int mRestoredListPosition; Bucket mBucket; @@ -50,11 +52,18 @@ public View onCreateView(@Nonnull LayoutInflater inflater, ViewGroup container, } @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.setDivider(null); @@ -64,13 +73,12 @@ public void onViewCreated(View view, Bundle savedInstanceState) { listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); } - if (mBucket != null && mNotesAdapter == null) { + if (mNotesAdapter == null) { mNotesAdapter = new NotesAdapter(getActivity(), mBucket); - setListAdapter(mNotesAdapter); - } else if (mBucket == null) { - ToastUtils.showToast(getActivity(), R.string.error_refresh_notifications); } + setListAdapter(mNotesAdapter); + // Set empty text if no notifications TextView textview = (TextView) listView.getEmptyView(); if (textview != null) { @@ -78,14 +86,6 @@ 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(); @@ -178,7 +178,9 @@ public void run() { mNotesAdapter.reloadNotes(); updateLastSeenTime(); - if (DisplayUtils.isLandscapeTablet(getActivity()) && getListView() != null) { + if (getListView() == null) return; + + if (DisplayUtils.isLandscapeTablet(getActivity())) { // Select first row on a landscape tablet if (mShouldLoadFirstNote) { mShouldLoadFirstNote = false; @@ -191,12 +193,25 @@ public void run() { if (mSelectedNoteId != null) { new HighlightSelectedNoteTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + restoreListScrollPosition(); } + } else { + restoreListScrollPosition(); } } }); } + private void restoreListScrollPosition() { + if (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()) { @@ -210,6 +225,17 @@ public void setShouldLoadFirstNote(boolean shouldLoad) { mShouldLoadFirstNote = shouldLoad; } + 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 @@ -261,6 +287,7 @@ protected Integer doInBackground(Void... params) { protected void onPostExecute(Integer noteListPosition) { if (isAdded() && noteListPosition != ListView.INVALID_POSITION && getListView() != null) { getListView().setItemChecked(noteListPosition, true); + getListView().setSelectionFromTop(noteListPosition, 0); } mSelectedNoteId = null; From 88368deadbe3ce6fc50fd0a8d0896d99b6191c9c Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Tue, 9 Sep 2014 11:50:59 -0700 Subject: [PATCH 127/144] Comments/Notifications activities upgraded to use 'undo bar'. * CommentsActivity and NotificationsActivity now handle the moderation/reply actions instead of `CommentDetailFragment`. * Moderating a comment will dismiss `CommentDetailFragment` and show the undo bar for destructive actions. * Undo basically hides the comment that's being moderated until after the undo bar disappears, then the request is made. Tapping undo 'unhides' the comment/notification. * `NotificationsActivity` and `CommentsActivity` have both had the tablet landscape dual-pane mode removed. --- .../android/ui/comments/CommentActions.java | 29 +- .../android/ui/comments/CommentAdapter.java | 46 ++- .../ui/comments/CommentDetailFragment.java | 257 ++------------- .../android/ui/comments/CommentsActivity.java | 305 ++++++++---------- .../ui/comments/CommentsListFragment.java | 49 +-- .../ui/notifications/NotesAdapter.java | 44 ++- .../notifications/NotificationsActivity.java | 142 +++++--- .../NotificationsListFragment.java | 96 ++---- .../main/res/drawable/calypso_bordered_bg.xml | 14 + .../layout-sw600dp-land/comment_activity.xml | 39 --- .../notifications_activity.xml | 29 -- .../res/layout-sw720dp/comment_activity.xml | 39 --- .../src/main/res/layout/comment_activity.xml | 2 +- .../notifications_fragment_notes_list.xml | 6 +- .../res/layout/notifications_list_item.xml | 176 +++++----- .../res/layout/reader_include_comment_box.xml | 4 +- .../main/res/values-sw600dp-land/dimens.xml | 1 + .../src/main/res/values-sw600dp/dimens.xml | 2 + .../src/main/res/values-sw600dp/styles.xml | 10 + WordPress/src/main/res/values/strings.xml | 2 + WordPress/src/main/res/values/styles.xml | 7 +- 21 files changed, 510 insertions(+), 789 deletions(-) create mode 100644 WordPress/src/main/res/drawable/calypso_bordered_bg.xml delete mode 100644 WordPress/src/main/res/layout-sw600dp-land/comment_activity.xml delete mode 100644 WordPress/src/main/res/layout-sw600dp-land/notifications_activity.xml delete mode 100644 WordPress/src/main/res/layout-sw720dp/comment_activity.xml create mode 100644 WordPress/src/main/res/values-sw600dp/styles.xml 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 5335d55df694..b38931c5fb56 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 @@ -33,7 +33,6 @@ public class CommentActions { - private static long mModeratingCommentId; private static String mModeratingNoteId; private CommentActions() { @@ -62,7 +61,17 @@ public static enum ChangedFrom {COMMENT_LIST, COMMENT_DETAIL} public static enum ChangeType {EDITED, STATUS, REPLIED, TRASHED, SPAMMED} public static interface OnCommentChangeListener { public void onCommentChanged(ChangedFrom changedFrom, ChangeType changeType); - public void onModerationCompleted(boolean success); + public void onCommentActionCompleted(ChangeType changeType, boolean success); + } + + public static interface OnCommentActionListener { + public void onModerateComment(int accountId, Comment comment, CommentStatus newStatus); + public void onReplyToComment(int accountId, Comment comment, String replyText); + } + + public static interface OnNoteCommentActionListener { + public void onModerateCommentForNote(Note note, CommentStatus oldStatus, CommentStatus newStatus); + public void onReplyToNote(Note note, String replyText); } @@ -207,7 +216,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)) { @@ -309,7 +318,6 @@ public void run() { Long.toString(comment.commentID), postHash}; - mModeratingCommentId = comment.commentID; Object result; try { result = client.call("wp.editComment", params); @@ -323,7 +331,6 @@ public void run() { AppLog.e(T.COMMENTS, "Error while editing comment", e); result = null; } - mModeratingCommentId = -1; final boolean success = (result != null && Boolean.parseBoolean(result.toString())); if (success) @@ -448,8 +455,6 @@ public void run() { blog.getPassword(), comment.commentID }; - mModeratingCommentId = comment.commentID; - Object result; try { result = client.call("wp.deleteComment", params); @@ -464,8 +469,6 @@ public void run() { result = null; } - mModeratingCommentId = -1; - final boolean success = (result != null && Boolean.parseBoolean(result.toString())); if (success) CommentTable.deleteComment(accountId, comment.commentID); @@ -482,14 +485,6 @@ public void run() { }.start(); } - static long getModeratingCommentId() { - return mModeratingCommentId; - } - - public static String getModeratingNoteId() { - return mModeratingNoteId; - } - /** * delete multiple comments */ 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 7cefa5a86167..b861fae04fe5 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 @@ -8,7 +8,6 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; -import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; @@ -20,10 +19,11 @@ import org.wordpress.android.util.AniUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.DateTimeUtils; -import org.wordpress.android.util.ToastUtils; 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 { @@ -45,13 +45,12 @@ 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; @@ -70,7 +69,6 @@ static interface OnSelectedItemsChangeListener { mStatusColorSpam = context.getResources().getColor(R.color.comment_status_spam); mStatusColorUnapproved = context.getResources().getColor(R.color.comment_status_unapproved); - mSelectionColor = context.getResources().getColor(R.color.blue_extra_light); mStatusTextSpam = context.getResources().getString(R.string.comment_status_spam); mStatusTextUnapproved = context.getResources().getString(R.string.comment_status_unapproved); @@ -170,19 +168,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); } @@ -201,6 +200,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; @@ -213,7 +220,7 @@ public View getView(final int position, View convertView, ViewGroup parent) { holder = (CommentHolder) convertView.getTag(); } - if (CommentActions.getModeratingCommentId() == comment.commentID) { + if (isModeratingCommentId(comment.commentID)) { holder.progressBar.setVisibility(View.VISIBLE); } else { holder.progressBar.setVisibility(View.GONE); @@ -242,24 +249,15 @@ public View getView(final int position, View convertView, ViewGroup parent) { } holder.txtStatus.setVisibility(showStatus ? View.VISIBLE : View.GONE); - final boolean useSelectionBackground; 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); } - if (useSelectionBackground) { - convertView.setBackgroundColor(mSelectionColor); - } else { - convertView.setBackgroundDrawable(null); - } - // comment text needs to be to the left of date/status when the title is a single line and // the status is displayed or else the status may overlap the comment text - note that // getLineCount() will return 0 if the view hasn't been rendered yet, which is why we 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 71a8401cf070..7b516e117b02 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,11 +1,9 @@ package org.wordpress.android.ui.comments; import android.app.Activity; -import android.app.AlertDialog; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; -import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.text.Html; @@ -39,6 +37,8 @@ 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; @@ -54,8 +54,6 @@ import org.wordpress.android.util.GravatarUtils; import org.wordpress.android.util.HtmlUtils; import org.wordpress.android.util.PhotonUtils; -import org.wordpress.android.util.ToastUtils; -import org.wordpress.android.util.VolleyUtils; import org.wordpress.android.util.WPLinkMovementMethod; import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.widgets.WPNetworkImageView; @@ -73,8 +71,6 @@ public class CommentDetailFragment extends Fragment implements NotificationFragm private Comment mComment; private Note mNote; - private NotificationsDetailListFragment mDetailListFragment; - private TextView mTxtStatus; private TextView mTxtContent; private ImageView mImgSubmitReply; @@ -97,13 +93,12 @@ public class CommentDetailFragment extends Fragment implements NotificationFragm private String mRestoredReplyText; - private boolean mIsSubmittingReply = false; - private boolean mIsModeratingComment = false; - private boolean mIsRequestingComment = false; private boolean mIsUsersBlog = false; 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"; @@ -215,7 +210,7 @@ public void onClick(View v) { mBtnSpamComment.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - onSpamButtonClick(); + moderateComment(CommentStatus.SPAM); } }); @@ -230,7 +225,7 @@ public void onClick(View v) { mBtnTrashComment.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - confirmDeleteComment(); + moderateComment(CommentStatus.TRASH); } }); @@ -246,16 +241,6 @@ public void onClick(View v) { return view; } - private void onSpamButtonClick() { - if (mComment == null) return; - - if (mComment.getStatusEnum() == CommentStatus.SPAM) { - moderateComment(CommentStatus.APPROVED); - } else { - confirmSpamComment(); - } - } - private View.OnClickListener mMoreBtnClickListener = new View.OnClickListener() { @Override @@ -267,7 +252,7 @@ public void onClick(View v) { menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - onSpamButtonClick(); + moderateComment(CommentStatus.SPAM); return true; } }); @@ -328,6 +313,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 @@ -427,7 +416,7 @@ private void showComment() { layoutBottom.setVisibility(View.GONE); // if a notification was passed, request its associated comment - if (mNote != null && !mIsRequestingComment) { + if (mNote != null) { showCommentForNote(mNote); } @@ -592,133 +581,20 @@ 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 confirmSpamComment() { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setMessage(R.string.dlg_confirm_spam_comment); - builder.setTitle(R.string.comment_status_spam); - builder.setCancelable(true); - builder.setPositiveButton(R.string.comment_mark_spam, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - moderateComment(CommentStatus.SPAM); - } - }); - builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - AlertDialog alert = builder.create(); - alert.show(); - } - /* * 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; - // disable buttons during request - mLayoutButtons.setEnabled(false); - - // 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() { - @Override - public void onActionResult(boolean succeeded) { - mIsModeratingComment = false; - if (isAdded()) { - mLayoutButtons.setEnabled(true); - if (succeeded) { - mComment.setStatus(CommentStatus.toString(newStatus)); - // If approved or unapproved - switch (newStatus) { - case APPROVED: - if (mOnCommentChangeListener != null) { - mOnCommentChangeListener.onCommentChanged(ChangedFrom.COMMENT_DETAIL, ChangeType.STATUS); - } - AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATION_APPROVED); - break; - case UNAPPROVED: - if (mOnCommentChangeListener != null) { - mOnCommentChangeListener.onCommentChanged(ChangedFrom.COMMENT_DETAIL, ChangeType.STATUS); - } - break; - } - } 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(); - } - } else { - if (mOnCommentChangeListener != null) { - mOnCommentChangeListener.onModerationCompleted(succeeded); - } - } - } - }; - - 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); - } - - // For destructive actions, we'll fire the listener straight away which will close this fragment - switch (newStatus) { - case SPAM: - if (mOnCommentChangeListener != null) { - mOnCommentChangeListener.onCommentChanged(ChangedFrom.COMMENT_DETAIL, ChangeType.SPAMMED); - } - AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATION_FLAGGED_AS_SPAM); - break; - case TRASH: - if (mOnCommentChangeListener != null) { - mOnCommentChangeListener.onCommentChanged(ChangedFrom.COMMENT_DETAIL, ChangeType.TRASHED); - } - AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATION_TRASHED); - break; - } - - if (mDetailListFragment != null) { - mDetailListFragment.refreshBlocksForCommentStatus(newStatus); + // Fire the appropriate listener + if (mNote != null && mOnNoteCommentActionListener != null) { + mOnNoteCommentActionListener.onModerateCommentForNote(mNote, mNote.getCommentStatus(), newStatus); + } else if (mOnCommentActionListener != null) { + mOnCommentActionListener.onModerateComment(mLocalBlogId, mComment, newStatus); } } @@ -726,7 +602,7 @@ public void onActionResult(boolean succeeded) { * post comment box text as a reply to the current comment */ private void submitReply() { - if (!isAdded() || mIsSubmittingReply) + if (!isAdded()) return; if (!NetworkUtils.checkConnection(getActivity())) @@ -736,43 +612,20 @@ private void submitReply() { if (TextUtils.isEmpty(replyText)) return; + AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATION_REPLIED_TO); + // disable editor, hide soft keyboard, hide submit icon, and show progress spinner while submitting mEditReply.setEnabled(false); EditTextUtils.hideSoftInput(mEditReply); mImgSubmitReply.setVisibility(View.GONE); - final ProgressBar progress = (ProgressBar) getView().findViewById(R.id.progress_submit_comment); + ProgressBar progress = (ProgressBar) getView().findViewById(R.id.progress_submit_comment); progress.setVisibility(View.VISIBLE); - CommentActions.CommentActionListener actionListener = new CommentActions.CommentActionListener() { - @Override - public void onActionResult(boolean succeeded) { - mIsSubmittingReply = false; - if (succeeded && mOnCommentChangeListener != null) - mOnCommentChangeListener.onCommentChanged(ChangedFrom.COMMENT_DETAIL, ChangeType.REPLIED); - if (isAdded()) { - mEditReply.setEnabled(true); - mImgSubmitReply.setVisibility(View.VISIBLE); - progress.setVisibility(View.GONE); - updateStatusViews(); - if (succeeded) { - ToastUtils.showToast(getActivity(), getString(R.string.note_reply_successful)); - mEditReply.setText(null); - } else { - ToastUtils.showToast(getActivity(), R.string.reply_failed, ToastUtils.Duration.LONG); - // refocus editor on failure and show soft keyboard - EditTextUtils.showSoftInput(mEditReply); - } - } - } - }; - - mIsSubmittingReply = true; - - AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATION_REPLIED_TO); - if (mNote != null) { - CommentActions.submitReplyToCommentNote(mNote, replyText, actionListener); - } else { - CommentActions.submitReplyToComment(mLocalBlogId, mComment, replyText, actionListener); + // Fire the appropriate action listener + if (mNote != null && mOnNoteCommentActionListener != null) { + mOnNoteCommentActionListener.onReplyToNote(mNote, replyText); + } else if (mOnCommentActionListener != null) { + mOnCommentActionListener.onReplyToComment(mLocalBlogId, mComment, replyText); } } @@ -850,6 +703,7 @@ private void updateStatusViews() { 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); @@ -962,9 +816,9 @@ private void showCommentForNote(Note note) { // Now we'll add a detail fragment list FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - mDetailListFragment = NotificationsDetailListFragment.newInstance(note); - mDetailListFragment.setFooterView(mLayoutButtons); - fragmentTransaction.add(R.id.comment_content_container, mDetailListFragment); + NotificationsDetailListFragment detailListFragment = NotificationsDetailListFragment.newInstance(note); + detailListFragment.setFooterView(mLayoutButtons); + fragmentTransaction.add(R.id.comment_content_container, detailListFragment); fragmentTransaction.commitAllowingStateLoss(); /* @@ -983,58 +837,9 @@ private void showCommentForNote(Note note) { // 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 build it from the note data setComment(localBlogId, note.buildComment()); } - /* - * 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 - * displayed from a notification (and notifications require wp.com/Jetpack) - */ - 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) - progress.setVisibility(View.VISIBLE); - - RestRequest.Listener restListener = new RestRequest.Listener() { - @Override - public void onResponse(JSONObject jsonObject) { - mIsRequestingComment = false; - if (isAdded()) { - 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) - CommentTable.addComment(localBlogId, comment); - // now, at long last, show the comment - setComment(localBlogId, comment); - } - } - } - }; - 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) - 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); - } - // Like or unlike a comment via the REST API private void likeComment() { if (mNote == null) return; 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 576bb092cb90..36e60adaa831 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,14 +7,19 @@ 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; @@ -23,33 +28,29 @@ 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.OnCommentChangeListener { - private static final String KEY_HIGHLIGHTED_COMMENT_ID = "highlighted_comment_id"; + CommentActions.OnCommentActionListener { 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); } @@ -57,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); @@ -83,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(); @@ -132,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; } @@ -151,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); } } }; @@ -189,52 +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 SPAMMED: - case TRASHED: - //updateCommentList(); - - // remove the detail view since comment was trashed/spammed - FragmentManager fm = getFragmentManager(); - if (fm.getBackStackEntryCount() > 0) { - fm.popBackStack(); - } - break; - case REPLIED: - updateCommentList(); - break; - default: - reloadCommentList(); - break; - } - break; - } - } - - // CommentDetailFragment will call this after moderation completes if it is no longer added - @Override - public void onModerationCompleted(boolean success) { - if (isFinishing()) return; - - if (success) { - updateCommentList(); - } else { - ToastUtils.showToast(this, R.string.error_moderate_comment, ToastUtils.Duration.LONG); - } - } private CommentDetailFragment getDetailFragment() { Fragment fragment = getFragmentManager().findFragmentByTag(getString( @@ -262,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(); @@ -300,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); } /* @@ -339,14 +210,7 @@ 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 @@ -369,7 +233,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); @@ -379,14 +243,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); @@ -399,4 +257,103 @@ 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) { + 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) { + if (isFinishing()) return; + CommentActions.moderateComment(accountId, comment, newStatus, + new CommentActions.CommentActionListener() { + @Override + public void onActionResult(boolean succeeded) { + 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 + ); + } + } + }); + } + + @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 onReplyToComment(int accountId, final Comment comment, String replyText) { + FragmentManager fm = getFragmentManager(); + if (fm.getBackStackEntryCount() > 0) { + fm.popBackStack(); + } + + getListFragment().setCommentIsModerating(comment.commentID, true); + CommentActions.submitReplyToComment(accountId, comment, replyText, new CommentActions.CommentActionListener() { + @Override + public void onActionResult(boolean succeeded) { + if (isFinishing()) return; + + getListFragment().setCommentIsModerating(comment.commentID, false); + + if (succeeded) { + getListFragment().updateComments(false); + ToastUtils.showToast(CommentsActivity.this, R.string.note_reply_successful); + } else { + ToastUtils.showToast(CommentsActivity.this, R.string.reply_failed); + } + } + }); + } } 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/notifications/NotesAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotesAdapter.java index 177e29ff0a98..6dcf2416357b 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 @@ -1,5 +1,6 @@ package org.wordpress.android.ui.notifications; +import android.animation.Animator; import android.content.Context; import android.database.Cursor; import android.text.Html; @@ -7,6 +8,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; import android.widget.CursorAdapter; import android.widget.ProgressBar; import android.widget.TextView; @@ -24,6 +26,9 @@ import org.wordpress.android.widgets.NoticonTextView; import org.wordpress.android.widgets.WPNetworkImageView; +import java.util.ArrayList; +import java.util.List; + class NotesAdapter extends CursorAdapter { final private int mAvatarSz; @@ -31,6 +36,8 @@ class NotesAdapter extends CursorAdapter { private final Bucket mNotesBucket; private int mReadBackgroundResId; private int mUnreadBackgroundResId; + private List mHiddenNoteIds = new ArrayList(); + private List mModeratingNoteIds = new ArrayList(); NotesAdapter(Context context, Bucket bucket) { super(context, null, 0x0); @@ -47,6 +54,7 @@ class NotesAdapter extends CursorAdapter { 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; @@ -82,6 +90,25 @@ public void reloadNotes() { changeCursor(mQuery.execute()); } + public void addHiddenNoteId(String noteId) { + mHiddenNoteIds.add(noteId); + notifyDataSetChanged(); + } + + 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) { @@ -99,7 +126,7 @@ public void bindView(View view, Context context, Cursor cursor) { Bucket.ObjectCursor objectCursor = (Bucket.ObjectCursor) cursor; - NoteViewHolder noteViewHolder = (NoteViewHolder) view.getTag(); + final NoteViewHolder noteViewHolder = (NoteViewHolder) view.getTag(); // Display group header Note.NoteTimeGroup timeGroup = Note.getTimeGroupForTimestamp(getLongForColumnName(objectCursor, Note.Schema.TIMESTAMP_INDEX)); @@ -128,8 +155,13 @@ public void bindView(View view, Context context, Cursor cursor) { noteViewHolder.headerView.setVisibility(View.VISIBLE); } - if (CommentActions.getModeratingNoteId() != null && - CommentActions.getModeratingNoteId().equals(objectCursor.getSimperiumKey())) { + 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); @@ -201,8 +233,13 @@ private long getLongForColumnName(Cursor cursor, String columnName) { return cursor.getLong(cursor.getColumnIndex(columnName)); } + public boolean isModeratingNote(String noteId) { + return mModeratingNoteIds.contains(noteId); + } + public static class NoteViewHolder { private final View headerView; + private final View contentView; private final TextView headerText; private final TextView txtLabel; @@ -213,6 +250,7 @@ public static class NoteViewHolder { NoteViewHolder(View view) { 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); 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 c79ac4e018b3..d9628e3f3f09 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 @@ -7,18 +7,22 @@ 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.widget.ListView; +import com.cocosw.undobar.UndoBarController; import com.simperium.client.Bucket; import com.simperium.client.BucketObjectMissingException; import org.wordpress.android.GCMIntentService; import org.wordpress.android.R; import org.wordpress.android.analytics.AnalyticsTracker; +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.CommentActions; @@ -31,13 +35,11 @@ import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.AuthenticationDialogUtils; -import org.wordpress.android.util.DisplayUtils; import org.wordpress.android.util.ToastUtils; import javax.annotation.Nonnull; -public class NotificationsActivity extends WPActionBarActivity - implements CommentActions.OnCommentChangeListener { +public class NotificationsActivity extends WPActionBarActivity implements CommentActions.OnNoteCommentActionListener { 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"; @@ -179,8 +181,6 @@ private void launchWithNoteId() { Intent intent = getIntent(); if (intent.hasExtra(NOTE_ID_EXTRA)) { openNoteForNoteId(intent.getStringExtra(NOTE_ID_EXTRA)); - } else if (DisplayUtils.isLandscapeTablet(this) && mNotesListFragment != null) { - mNotesListFragment.setShouldLoadFirstNote(true); } } @@ -201,37 +201,6 @@ private void openNoteForNoteId(String noteId) { } } - /* - * 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) { - if (isFinishing()) return; - - // remove the comment detail fragment if the comment was trashed or spammed - if ((changeType == CommentActions.ChangeType.TRASHED || changeType == CommentActions.ChangeType.SPAMMED) - && changedFrom == CommentActions.ChangedFrom.COMMENT_DETAIL) { - FragmentManager fm = getFragmentManager(); - if (fm.getBackStackEntryCount() > 0) { - fm.popBackStack(); - } - } - - mNotesListFragment.refreshNotes(); - } - - // CommentDetailFragment will call this after moderation completes if it is no longer added - @Override - public void onModerationCompleted(boolean success) { - if (isFinishing()) return; - - mNotesListFragment.refreshNotes(); - if (!success) { - ToastUtils.showToast(this, R.string.error_moderate_comment, ToastUtils.Duration.LONG); - } - } - void popNoteDetail() { FragmentManager fm = getFragmentManager(); fm.popBackStack(); @@ -289,15 +258,6 @@ private void openNote(final Note note) { // create detail fragment for this note type mDetailFragment = getDetailFragmentForNote(note); - if (DisplayUtils.isLandscapeTablet(this)) { - FragmentManager fm = getFragmentManager(); - FragmentTransaction ft = fm.beginTransaction(); - ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); - ft.replace(R.id.notifications_detail_fragment_container, mDetailFragment, TAG_TABLET_DETAIL_VIEW); - ft.commitAllowingStateLoss(); - return; - } - FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); @@ -342,6 +302,98 @@ public void showWebViewActivityForUrl(String url) { startActivity(intent); } + @Override + public void onModerateCommentForNote(final Note note, final CommentStatus oldStatus, final CommentStatus newStatus) { + FragmentManager fm = getFragmentManager(); + if (fm.getBackStackEntryCount() > 0) { + fm.popBackStack(); + } + + 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(); + } + } + + @Override + public void onReplyToNote(final Note note, String replyText) { + if (note == null || TextUtils.isEmpty(replyText)) return; + + FragmentManager fm = getFragmentManager(); + if (fm.getBackStackEntryCount() > 0) { + fm.popBackStack(); + } + + mNotesListFragment.setNoteIsModerating(note.getId(), true); + CommentActions.submitReplyToCommentNote(note, replyText, 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.note_reply_successful); + } else { + ToastUtils.showToast(NotificationsActivity.this, R.string.reply_failed); + } + } + }); + } + private class NoteClickListener implements NotificationsListFragment.OnNoteClickListener { @Override public void onClickNote(Note note, int position) { 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 cc0a1f3f6230..5bf86386b1be 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,14 +1,11 @@ package org.wordpress.android.ui.notifications; import android.app.ListFragment; -import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AbsListView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; @@ -33,7 +30,6 @@ public class NotificationsListFragment extends ListFragment implements Bucket.Li private NotesAdapter mNotesAdapter; private OnNoteClickListener mNoteClickListener; - private boolean mShouldLoadFirstNote; private String mSelectedNoteId; private int mRestoredListPosition; @@ -68,10 +64,6 @@ public void onActivityCreated(Bundle savedInstanceState) { ListView listView = getListView(); listView.setDivider(null); listView.setDividerHeight(0); - listView.setBackgroundColor(getResources().getColor(R.color.white)); - if (DisplayUtils.isLandscapeTablet(getActivity())) { - listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); - } if (mNotesAdapter == null) { mNotesAdapter = new NotesAdapter(getActivity(), mBucket); @@ -138,6 +130,11 @@ public void onListItemClick(ListView l, View v, int position, long id) { if (!isAdded()) return; Note note = mNotesAdapter.getNote(position); + + if (mNotesAdapter.isModeratingNote(note.getId())) { + return; + } + if (note != null && mNoteClickListener != null) { mNoteClickListener.onClickNote(note, position); } @@ -151,6 +148,26 @@ public void setOnNoteClickListener(OnNoteClickListener listener) { mNoteClickListener = listener; } + 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); + } + } + protected void updateLastSeenTime() { // set the timestamp to now try { @@ -178,33 +195,13 @@ public void run() { mNotesAdapter.reloadNotes(); updateLastSeenTime(); - if (getListView() == null) return; - - if (DisplayUtils.isLandscapeTablet(getActivity())) { - // Select first row on a landscape tablet - if (mShouldLoadFirstNote) { - mShouldLoadFirstNote = false; - Note note = mNotesAdapter.getNote(0); - if (note != null) { - mNoteClickListener.onClickNote(note, 0); - mSelectedNoteId = note.getId(); - } - } - - if (mSelectedNoteId != null) { - new HighlightSelectedNoteTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - restoreListScrollPosition(); - } - } else { - restoreListScrollPosition(); - } + restoreListScrollPosition(); } }); } private void restoreListScrollPosition() { - if (mRestoredListPosition != ListView.INVALID_POSITION + if (getListView() != null && mRestoredListPosition != ListView.INVALID_POSITION && mRestoredListPosition < mNotesAdapter.getCount()) { // Restore scroll position in list getListView().setSelectionFromTop(mRestoredListPosition, 0); @@ -221,10 +218,6 @@ public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } - public void setShouldLoadFirstNote(boolean shouldLoad) { - mShouldLoadFirstNote = shouldLoad; - } - public int getScrollPosition() { if (!isAdded() || getListView() == null) { return ListView.INVALID_POSITION; @@ -259,39 +252,4 @@ public void onChange(Bucket bucket, Bucket.ChangeType type, String key) { public void onBeforeUpdateObject(Bucket noteBucket, Note note) { //noop } - - // Retrieve the index for the selected note and set it to be highlighted (for landscape tablets only) - private class HighlightSelectedNoteTask extends AsyncTask { - - @Override - protected Integer doInBackground(Void... params) { - if (TextUtils.isEmpty(mSelectedNoteId) || mNotesAdapter == null) { - return ListView.INVALID_POSITION; - } - - Bucket.ObjectCursor cursor = (Bucket.ObjectCursor) mNotesAdapter.getCursor(); - if (cursor != null) { - for (int i = 0; i < cursor.getCount(); i++) { - cursor.moveToPosition(i); - String noteSimperiumId = cursor.getSimperiumKey(); - if (noteSimperiumId != null && noteSimperiumId.equals(mSelectedNoteId)) { - return i; - } - } - } - - return ListView.INVALID_POSITION; - } - - @Override - protected void onPostExecute(Integer noteListPosition) { - if (isAdded() && noteListPosition != ListView.INVALID_POSITION && getListView() != null) { - getListView().setItemChecked(noteListPosition, true); - getListView().setSelectionFromTop(noteListPosition, 0); - } - - mSelectedNoteId = null; - } - } - } 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/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_activity.xml b/WordPress/src/main/res/layout-sw600dp-land/notifications_activity.xml deleted file mode 100644 index 025f12d566bd..000000000000 --- a/WordPress/src/main/res/layout-sw600dp-land/notifications_activity.xml +++ /dev/null @@ -1,29 +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/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 @@ + android:layout_height="match_parent"> @@ -21,8 +21,8 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:paddingTop="@dimen/margin_small" - android:paddingBottom="@dimen/margin_small"> + android:paddingBottom="@dimen/margin_small" + android:paddingTop="@dimen/margin_small"> + android:textSize="18sp" + tools:text="\uf303" /> @@ -52,93 +52,103 @@ + android:orientation="vertical"> - + + + + + + + + + + + + + - - + + + + + android:layout_marginTop="-1dp" + android:ellipsize="end" + android:maxLines="2" + android:textColor="@color/calypso_blue" + android:textSize="@dimen/text_sz_large" + android:visibility="gone" + tools:text="What an amazing post!" + tools:visibility="visible" /> - - - - - - - - - - + - + \ No newline at end of file diff --git a/WordPress/src/main/res/layout/reader_include_comment_box.xml b/WordPress/src/main/res/layout/reader_include_comment_box.xml index da5377adeafe..1c98a734254e 100644 --- a/WordPress/src/main/res/layout/reader_include_comment_box.xml +++ b/WordPress/src/main/res/layout/reader_include_comment_box.xml @@ -15,8 +15,8 @@ + android:layout_height="1dp" + android:background="@color/calypso_blue_border" /> @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/strings.xml b/WordPress/src/main/res/values/strings.xml index 6d3ef4c2a8e2..3555b460eec0 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -196,6 +196,8 @@ Reply to %s Mark this comment as spam? Mark spam + Comment trashed + Comment spammed Approve diff --git a/WordPress/src/main/res/values/styles.xml b/WordPress/src/main/res/values/styles.xml index 7cc6a2e1d7a6..bf8b325e4a41 100644 --- a/WordPress/src/main/res/values/styles.xml +++ b/WordPress/src/main/res/values/styles.xml @@ -1,6 +1,5 @@ - + + +