diff options
author | Danilo Ercoli <ercoli@gmail.com> | 2015-02-25 11:25:24 +0100 |
---|---|---|
committer | Danilo Ercoli <ercoli@gmail.com> | 2015-02-25 11:25:24 +0100 |
commit | 4e0ed816b5c184296d8a23d34c31375d20b60e4b (patch) | |
tree | 35126f11e8cff4703de83e02fccd4841b21f4154 /WordPress/src/main/java/org/wordpress | |
parent | 84a2edcff87e94dc09c0c60a97cb35b5e469d3ac (diff) | |
parent | 99b3ffac1b8d328e6f52a001a7ae7e614a227835 (diff) | |
download | gradle-perf-android-medium-4e0ed816b5c184296d8a23d34c31375d20b60e4b.tar.gz |
Merge branch 'develop' of https://github.com/wordpress-mobile/WordPress-Android into issue/2347-new-approach-views-visitors
Diffstat (limited to 'WordPress/src/main/java/org/wordpress')
17 files changed, 440 insertions, 437 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/datasets/ReaderPostTable.java b/WordPress/src/main/java/org/wordpress/android/datasets/ReaderPostTable.java index 2e0860ed5..0a1231c81 100644 --- a/WordPress/src/main/java/org/wordpress/android/datasets/ReaderPostTable.java +++ b/WordPress/src/main/java/org/wordpress/android/datasets/ReaderPostTable.java @@ -576,9 +576,9 @@ public class ReaderPostTable { if (tag.tagType == ReaderTagType.DEFAULT) { // skip posts that are no longer liked if this is "Posts I Like", skip posts that are no // longer followed if this is "Blogs I Follow" - if (tag.getTagName().equals(ReaderTag.TAG_NAME_LIKED)) { + if (tag.isPostsILike()) { sql += " AND tbl_posts.is_liked != 0"; - } else if (tag.getTagName().equals(ReaderTag.TAG_NAME_FOLLOWING)) { + } else if (tag.isBlogsIFollow()) { sql += " AND tbl_posts.is_followed != 0"; } } @@ -630,9 +630,9 @@ public class ReaderPostTable { + " AND tbl_post_tags.tag_type=?"; if (tag.tagType == ReaderTagType.DEFAULT) { - if (tag.getTagName().equals(ReaderTag.TAG_NAME_LIKED)) { + if (tag.isPostsILike()) { sql += " AND tbl_posts.is_liked != 0"; - } else if (tag.getTagName().equals(ReaderTag.TAG_NAME_FOLLOWING)) { + } else if (tag.isBlogsIFollow()) { sql += " AND tbl_posts.is_followed != 0"; } } diff --git a/WordPress/src/main/java/org/wordpress/android/models/ReaderPost.java b/WordPress/src/main/java/org/wordpress/android/models/ReaderPost.java index 2673b1f05..9dcc95788 100644 --- a/WordPress/src/main/java/org/wordpress/android/models/ReaderPost.java +++ b/WordPress/src/main/java/org/wordpress/android/models/ReaderPost.java @@ -85,18 +85,25 @@ public class ReaderPost { post.shortUrl = JSONUtil.getString(json, "short_URL"); post.setBlogUrl(JSONUtil.getString(json, "site_URL")); - post.numReplies = json.optInt("comment_count"); post.numLikes = json.optInt("like_count"); post.isLikedByCurrentUser = JSONUtil.getBool(json, "i_like"); post.isFollowedByCurrentUser = JSONUtil.getBool(json, "is_following"); post.isRebloggedByCurrentUser = JSONUtil.getBool(json, "is_reblogged"); - post.isCommentsOpen = JSONUtil.getBool(json, "comments_open"); post.isExternal = JSONUtil.getBool(json, "is_external"); post.isPrivate = JSONUtil.getBool(json, "site_is_private"); post.isLikesEnabled = JSONUtil.getBool(json, "likes_enabled"); post.isSharingEnabled = JSONUtil.getBool(json, "sharing_enabled"); + JSONObject jsonDiscussion = json.optJSONObject("discussion"); + if (jsonDiscussion != null) { + post.isCommentsOpen = JSONUtil.getBool(jsonDiscussion, "comments_open"); + post.numReplies = jsonDiscussion.optInt("comment_count"); + } else { + post.isCommentsOpen = JSONUtil.getBool(json, "comments_open"); + post.numReplies = json.optInt("comment_count"); + } + // parse the author section assignAuthorFromJson(post, json.optJSONObject("author")); diff --git a/WordPress/src/main/java/org/wordpress/android/models/ReaderTag.java b/WordPress/src/main/java/org/wordpress/android/models/ReaderTag.java index 5e1697f32..5c86bbaca 100644 --- a/WordPress/src/main/java/org/wordpress/android/models/ReaderTag.java +++ b/WordPress/src/main/java/org/wordpress/android/models/ReaderTag.java @@ -6,21 +6,17 @@ import org.wordpress.android.ui.reader.utils.ReaderUtils; import org.wordpress.android.util.StringUtils; import java.io.Serializable; -import java.lang.Character; import java.util.regex.Pattern; public class ReaderTag implements Serializable { private String tagName; private String endpoint; - public ReaderTagType tagType; - - public static final String TAG_ID_FOLLOWING = "following"; - public static final String TAG_ID_LIKED = "liked"; + public final ReaderTagType tagType; // these are the default tag names, which aren't localized in the /read/menu/ response - public static final String TAG_NAME_LIKED = "Posts I Like"; public static final String TAG_NAME_FOLLOWING = "Blogs I Follow"; - public static final String TAG_NAME_FRESHLY_PRESSED = "Freshly Pressed"; + private static final String TAG_NAME_LIKED = "Posts I Like"; + private static final String TAG_NAME_FRESHLY_PRESSED = "Freshly Pressed"; private static final String TAG_NAME_DEFAULT = TAG_NAME_FRESHLY_PRESSED; public ReaderTag(String tagName, String endpoint, ReaderTagType tagType) { @@ -45,30 +41,14 @@ public class ReaderTag implements Serializable { public String getEndpoint() { return StringUtils.notNullStr(endpoint); } - public void setEndpoint(String endpoint) { + void setEndpoint(String endpoint) { this.endpoint = StringUtils.notNullStr(endpoint); } - /** - * Extract tag Id from endpoint, only works for ReaderTagType.DEFAULT - * - * @return a string Id if tagType is ReaderTagType.DEFAULT, empty string else - */ - public String getStringIdFromEndpoint() { - if (tagType != ReaderTagType.DEFAULT) { - return ""; - } - String[] splitted = endpoint.split("/"); - if (splitted != null && splitted.length > 0) { - return splitted[splitted.length - 1]; - } - return ""; - } - public String getTagName() { return StringUtils.notNullStr(tagName); } - public void setTagName(String name) { + void setTagName(String name) { this.tagName = StringUtils.notNullStr(name); } public String getCapitalizedTagName() { @@ -114,11 +94,8 @@ public class ReaderTag implements Serializable { */ private static final Pattern INVALID_CHARS = Pattern.compile("^.*[~#@*+%{}<>\\[\\]|\"\\_].*$"); public static boolean isValidTagName(String tagName) { - if (TextUtils.isEmpty(tagName)) - return false; - if (INVALID_CHARS.matcher(tagName).matches()) - return false; - return true; + return !TextUtils.isEmpty(tagName) + && !INVALID_CHARS.matcher(tagName).matches(); } /* @@ -164,9 +141,17 @@ public class ReaderTag implements Serializable { if (tag1 == null || tag2 == null) { return false; } - if (tag1.tagType != tag2.tagType) { - return false; - } - return (tag1.getSanitizedTagName().equalsIgnoreCase(tag2.getSanitizedTagName())); + return tag1.tagType == tag2.tagType + && tag1.getSanitizedTagName().equalsIgnoreCase(tag2.getSanitizedTagName()); + } + + public boolean isPostsILike() { + return getTagName().equals(TAG_NAME_LIKED); + } + public boolean isBlogsIFollow() { + return getTagName().equals(TAG_NAME_FOLLOWING); + } + public boolean isFreshlyPressed() { + return getTagName().equals(TAG_NAME_FRESHLY_PRESSED); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUploadService.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUploadService.java index 73e94c5b8..536c26bed 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUploadService.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUploadService.java @@ -17,6 +17,7 @@ import android.preference.PreferenceManager; import android.provider.MediaStore.Images; import android.provider.MediaStore.Video; import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.content.IntentCompat; import android.text.TextUtils; import android.webkit.MimeTypeMap; @@ -52,6 +53,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -88,15 +90,27 @@ public class PostUploadService extends Service { } @Override - public void onStart(Intent intent, int startId) { + public void onDestroy() { + super.onDestroy(); + // Cancel current task, it will reset post from "uploading" to "local draft" + if (mCurrentTask != null) { + AppLog.d(T.POSTS, "cancelling current upload task"); + mCurrentTask.cancel(true); + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { synchronized (mPostsList) { if (mPostsList.size() == 0 || mContext == null) { - this.stopSelf(); - return; + stopSelf(); + return START_NOT_STICKY; } } uploadNextPost(); + // We want this service to continue running until it is explicitly stopped, so return sticky. + return START_STICKY; } private FeatureSet synchronousGetFeatureSet() { @@ -120,7 +134,7 @@ public class PostUploadService extends Service { mCurrentTask = new UploadPostTask(); mCurrentTask.execute(mCurrentUploadingPost); } else { - this.stopSelf(); + stopSelf(); } } } @@ -134,11 +148,6 @@ public class PostUploadService extends Service { uploadNextPost(); } - public static boolean isUploading(Post post) { - return mCurrentUploadingPost != null && mCurrentUploadingPost.equals(post) || - mPostsList.size() > 0 && mPostsList.contains(post); - } - private class UploadPostTask extends AsyncTask<Post, Boolean, Boolean> { private Post mPost; private Blog mBlog; @@ -165,13 +174,22 @@ public class PostUploadService extends Service { WordPress.wpDB.deleteMediaFilesForPost(mPost); } else { WordPress.postUploadFailed(mPost.getLocalTableBlogId()); - mPostUploadNotifier.updateNotificationWithError(mErrorMessage, mIsMediaError, mPost.isPage(), mErrorUnavailableVideoPress); + mPostUploadNotifier.updateNotificationWithError(mErrorMessage, mIsMediaError, mPost.isPage(), + mErrorUnavailableVideoPress); } postUploaded(); } @Override + protected void onCancelled(Boolean aBoolean) { + super.onCancelled(aBoolean); + mPostUploadNotifier.updateNotificationWithError(mErrorMessage, mIsMediaError, mPost.isPage(), + mErrorUnavailableVideoPress); + WordPress.postUploadFailed(mPost.getLocalTableBlogId()); + } + + @Override protected Boolean doInBackground(Post... posts) { mErrorUnavailableVideoPress = false; mPost = posts[0]; @@ -478,6 +496,8 @@ public class PostUploadService extends Service { } private String uploadImage(MediaFile mediaFile) { + AppLog.d(T.POSTS, "uploadImage: " + mediaFile.getFilePath()); + if (mediaFile.getFilePath() == null) { return null; } @@ -594,7 +614,8 @@ public class PostUploadService extends Service { } String fullSizeUrl = null; - // Upload the full size picture if "Original Size" is selected in settings, or if 'link to full size' is checked. + // Upload the full size picture if "Original Size" is selected in settings, + // or if 'link to full size' is checked. if (!shouldUploadResizedVersion || mBlog.isFullSizeImage()) { Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put("name", fileName); @@ -632,7 +653,8 @@ public class PostUploadService extends Service { String mimeType = "", xRes = "", yRes = ""; if (videoUri.toString().contains("content:")) { - String[] projection = new String[]{Video.Media._ID, Video.Media.DATA, Video.Media.MIME_TYPE, Video.Media.RESOLUTION}; + String[] projection = new String[]{Video.Media._ID, Video.Media.DATA, Video.Media.MIME_TYPE, + Video.Media.RESOLUTION}; Cursor cur = mContext.getContentResolver().query(videoUri, projection, null, null, null); if (cur != null && cur.moveToFirst()) { @@ -729,15 +751,14 @@ public class PostUploadService extends Service { private void setUploadPostErrorMessage(Exception e) { - mErrorMessage = String.format(mContext.getResources().getText(R.string.error_upload).toString(), mPost.isPage() ? mContext - .getResources().getText(R.string.page).toString() : mContext.getResources().getText(R.string.post).toString()) - + " " + e.getMessage(); + mErrorMessage = String.format(mContext.getResources().getText(R.string.error_upload).toString(), + mPost.isPage() ? mContext.getResources().getText(R.string.page).toString() : + mContext.getResources().getText(R.string.post).toString()) + " " + e.getMessage(); mIsMediaError = false; AppLog.e(T.EDITOR, mErrorMessage, e); } private String uploadImageFile(Map<String, Object> pictureParams, MediaFile mf, Blog blog) { - // create temporary upload file File tempFile; try { @@ -775,14 +796,17 @@ public class PostUploadService extends Service { } private Object uploadFileHelper(Object[] params, final File tempFile) { + AppLog.d(T.POSTS, "uploadFileHelper: " + Arrays.toString(params)); + // Create listener for tracking upload progress in the notification if (mClient instanceof XMLRPCClient) { XMLRPCClient xmlrpcClient = (XMLRPCClient) mClient; xmlrpcClient.setOnBytesUploadedListener(new XMLRPCClient.OnBytesUploadedListener() { @Override public void onBytesUploaded(long uploadedBytes) { - if (tempFile.length() == 0) return; - + if (tempFile.length() == 0) { + return; + } float percentage = (uploadedBytes * 100) / tempFile.length(); mPostUploadNotifier.updateNotificationProgress(percentage); } @@ -805,8 +829,9 @@ public class PostUploadService extends Service { return null; } finally { // remove the temporary upload file now that we're done with it - if (tempFile != null && tempFile.exists()) + if (tempFile != null && tempFile.exists()) { tempFile.delete(); + } } } } @@ -821,31 +846,34 @@ public class PostUploadService extends Service { private final NotificationCompat.Builder mNotificationBuilder; private final int mNotificationId; + private int mNotificationErrorId = 0; private int mTotalMediaItems; private int mCurrentMediaItem; private float mItemProgressSize; public PostUploadNotifier(Post post) { // add the uploader to the notification bar - mNotificationManager = (NotificationManager) SystemServiceFactory.get(mContext, Context.NOTIFICATION_SERVICE); + mNotificationManager = (NotificationManager) SystemServiceFactory.get(mContext, + Context.NOTIFICATION_SERVICE); - mNotificationBuilder = - new NotificationCompat.Builder(getApplicationContext()) - .setSmallIcon(android.R.drawable.stat_sys_upload); + mNotificationBuilder = new NotificationCompat.Builder(getApplicationContext()); + mNotificationBuilder.setSmallIcon(android.R.drawable.stat_sys_upload); Intent notificationIntent = new Intent(mContext, post.isPage() ? PagesActivity.class : PostsActivity.class); - notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP - | Intent.FLAG_ACTIVITY_NEW_TASK + notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK); notificationIntent.setAction(Intent.ACTION_MAIN); notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER); - notificationIntent.setData((Uri.parse("custom://wordpressNotificationIntent" + post.getLocalTableBlogId()))); + notificationIntent.setData((Uri.parse("custom://wordpressNotificationIntent" + + post.getLocalTableBlogId()))); notificationIntent.putExtra(PostsActivity.EXTRA_VIEW_PAGES, post.isPage()); - PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, + PendingIntent.FLAG_UPDATE_CURRENT); mNotificationBuilder.setContentIntent(pendingIntent); mNotificationId = (new Random()).nextInt() + post.getLocalTableBlogId(); + startForeground(mNotificationId, mNotificationBuilder.build()); } @@ -873,9 +901,13 @@ public class PostUploadService extends Service { mNotificationManager.cancel(mNotificationId); } - public void updateNotificationWithError(String mErrorMessage, boolean isMediaError, boolean isPage, boolean isVideoPressError) { - String postOrPage = (String) (isPage ? mContext.getResources().getText(R.string.page_id) : mContext.getResources() - .getText(R.string.post_id)); + public void updateNotificationWithError(String mErrorMessage, boolean isMediaError, boolean isPage, + boolean isVideoPressError) { + AppLog.d(T.POSTS, "updateNotificationWithError: " + mErrorMessage); + + Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext()); + String postOrPage = (String) (isPage ? mContext.getResources().getText(R.string.page_id) + : mContext.getResources().getText(R.string.post_id)); Intent notificationIntent = new Intent(mContext, isPage ? PagesActivity.class : PostsActivity.class); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK @@ -894,20 +926,27 @@ public class PostUploadService extends Service { String errorText = mContext.getResources().getText(R.string.upload_failed).toString(); if (isMediaError) { - errorText = mContext.getResources().getText(R.string.media) + " " + mContext.getResources().getText(R.string.error); + errorText = mContext.getResources().getText(R.string.media) + " " + + mContext.getResources().getText(R.string.error); } - mNotificationBuilder.setSmallIcon(android.R.drawable.stat_notify_error); - mNotificationBuilder.setContentTitle((isMediaError) ? errorText : mContext.getResources().getText(R.string.upload_failed)); - mNotificationBuilder.setContentText((isMediaError) ? mErrorMessage : postOrPage + " " + errorText + ": " + mErrorMessage); - mNotificationBuilder.setContentIntent(pendingIntent); - mNotificationBuilder.setAutoCancel(true); - - mNotificationManager.notify(mNotificationId, mNotificationBuilder.build()); + notificationBuilder.setSmallIcon(android.R.drawable.stat_notify_error); + notificationBuilder.setContentTitle((isMediaError) ? errorText : + mContext.getResources().getText(R.string.upload_failed)); + notificationBuilder.setContentText((isMediaError) ? mErrorMessage : postOrPage + " " + errorText + + ": " + mErrorMessage); + notificationBuilder.setContentIntent(pendingIntent); + notificationBuilder.setAutoCancel(true); + if (mNotificationErrorId == 0) { + mNotificationErrorId = mNotificationId + (new Random()).nextInt(); + } + mNotificationManager.notify(mNotificationErrorId, notificationBuilder.build()); } public void updateNotificationProgress(float progress) { - if (mTotalMediaItems == 0) return; + if (mTotalMediaItems == 0) { + return; + } // Simple way to show progress of entire post upload // Would be better if we could get total bytes for all media items. @@ -933,7 +972,8 @@ public class PostUploadService extends Service { public void setCurrentMediaItem(int currentItem) { mCurrentMediaItem = currentItem; - mNotificationBuilder.setContentText(String.format(getString(R.string.uploading_total), mCurrentMediaItem, mTotalMediaItems)); + mNotificationBuilder.setContentText(String.format(getString(R.string.uploading_total), mCurrentMediaItem, + mTotalMediaItems)); } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderEvents.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderEvents.java index f3c05e7b8..0c14d4018 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderEvents.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderEvents.java @@ -1,5 +1,9 @@ package org.wordpress.android.ui.reader; +import org.wordpress.android.util.DateTimeUtils; + +import java.util.Date; + /** * Reader-related EventBus event classes */ @@ -9,4 +13,17 @@ public class ReaderEvents { public static class FollowedBlogsChanged {} public static class RecommendedBlogsChanged {} + + public static class HasPurgedDatabase {} + public static class HasPerformedInitialUpdate {} + + public static class UpdatedFollowedTagsAndBlogs { + private Date mUpdateDate; + public UpdatedFollowedTagsAndBlogs() { + mUpdateDate = new Date(); + } + public int minutesSinceLastUpdate() { + return DateTimeUtils.minutesBetween(mUpdateDate, new Date()); + } + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java index 8de6fdeae..0cc84779c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java @@ -207,7 +207,7 @@ public class ReaderPostDetailFragment extends Fragment public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); menu.clear(); - inflater.inflate(R.menu.reader_native_detail, menu); + inflater.inflate(R.menu.reader_detail, menu); } @Override diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.java index 40cab085a..572a2a510 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.java @@ -4,70 +4,33 @@ import android.app.Activity; import android.app.Fragment; import android.content.Intent; import android.os.Bundle; -import android.view.MenuItem; import android.view.View; import org.wordpress.android.R; import org.wordpress.android.analytics.AnalyticsTracker; -import org.wordpress.android.datasets.ReaderDatabase; -import org.wordpress.android.datasets.ReaderPostTable; import org.wordpress.android.datasets.ReaderTagTable; import org.wordpress.android.models.ReaderTag; -import org.wordpress.android.models.ReaderTagType; import org.wordpress.android.ui.WPDrawerActivity; import org.wordpress.android.ui.accounts.WPComLoginActivity; import org.wordpress.android.ui.prefs.AppPrefs; -import org.wordpress.android.ui.reader.ReaderInterfaces.OnPostSelectedListener; -import org.wordpress.android.ui.reader.ReaderInterfaces.OnTagSelectedListener; -import org.wordpress.android.ui.reader.actions.ReaderAuthActions; -import org.wordpress.android.ui.reader.actions.ReaderUserActions; -import org.wordpress.android.ui.reader.services.ReaderUpdateService; -import org.wordpress.android.ui.reader.services.ReaderUpdateService.UpdateTask; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; -import org.wordpress.android.util.NetworkUtils; - -import java.util.EnumSet; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; +import de.greenrobot.event.EventBus; + /* * this activity serves as the host for ReaderPostListFragment */ -public class ReaderPostListActivity extends WPDrawerActivity - implements OnPostSelectedListener, - OnTagSelectedListener { - - private static boolean mHasPerformedInitialUpdate; - private static boolean mHasPerformedPurge; - - private final ScheduledExecutorService mUpdateScheduler = Executors.newScheduledThreadPool(1); - - private ReaderTypes.ReaderPostListType mPostListType; +public class ReaderPostListActivity extends WPDrawerActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); createMenuDrawer(R.layout.reader_activity_post_list); readIntent(getIntent(), savedInstanceState); - - // start updating tags & blogs after 500ms, then update them hourly - mUpdateScheduler.scheduleWithFixedDelay( - new Runnable() { - public void run() { - updateFollowedTagsAndBlogs(); - } - }, 500, (1000 * 60) * 60, TimeUnit.MILLISECONDS); - } - - @Override - protected void onDestroy() { - mUpdateScheduler.shutdownNow(); - super.onDestroy(); } private void readIntent(Intent intent, Bundle savedInstanceState) { @@ -75,14 +38,15 @@ public class ReaderPostListActivity extends WPDrawerActivity return; } + ReaderTypes.ReaderPostListType postListType; if (intent.hasExtra(ReaderConstants.ARG_POST_LIST_TYPE)) { - mPostListType = (ReaderTypes.ReaderPostListType) intent.getSerializableExtra(ReaderConstants.ARG_POST_LIST_TYPE); + postListType = (ReaderTypes.ReaderPostListType) intent.getSerializableExtra(ReaderConstants.ARG_POST_LIST_TYPE); } else { - mPostListType = ReaderTypes.DEFAULT_POST_LIST_TYPE; + postListType = ReaderTypes.DEFAULT_POST_LIST_TYPE; } // hide drawer toggle and enable back arrow click if this is blog preview or tag preview - if (mPostListType.isPreviewType() && getDrawerToggle() != null) { + if (postListType.isPreviewType() && getDrawerToggle() != null) { getDrawerToggle().setDrawerIndicatorEnabled(false); getToolbar().setNavigationOnClickListener(new View.OnClickListener() { @Override @@ -95,7 +59,7 @@ public class ReaderPostListActivity extends WPDrawerActivity if (savedInstanceState == null) { AnalyticsTracker.track(AnalyticsTracker.Stat.READER_ACCESSED); - if (mPostListType == ReaderTypes.ReaderPostListType.BLOG_PREVIEW) { + if (postListType == ReaderTypes.ReaderPostListType.BLOG_PREVIEW) { long blogId = intent.getLongExtra(ReaderConstants.ARG_BLOG_ID, 0); long feedId = intent.getLongExtra(ReaderConstants.ARG_FEED_ID, 0); if (feedId != 0) { @@ -112,15 +76,15 @@ public class ReaderPostListActivity extends WPDrawerActivity tag = AppPrefs.getReaderTag(); } // if this is a followed tag and it doesn't exist, revert to default tag - if (mPostListType == ReaderTypes.ReaderPostListType.TAG_FOLLOWED && !ReaderTagTable.tagExists(tag)) { + if (postListType == ReaderTypes.ReaderPostListType.TAG_FOLLOWED && !ReaderTagTable.tagExists(tag)) { tag = ReaderTag.getDefaultTag(); } - showListFragmentForTag(tag, mPostListType); + showListFragmentForTag(tag, postListType); } } - switch (mPostListType) { + switch (postListType) { case TAG_PREVIEW: setTitle(R.string.reader_title_tag_preview); break; @@ -132,27 +96,12 @@ public class ReaderPostListActivity extends WPDrawerActivity } // hide the static drawer for blog/tag preview - if (isStaticMenuDrawer() && mPostListType.isPreviewType()) { + if (isStaticMenuDrawer() && postListType.isPreviewType()) { hideDrawer(); } } @Override - protected void onStart() { - super.onStart(); - - // purge the database of older data at startup, but only if there's an active connection - // since we don't want to purge posts that the user would expect to see when offline - if (!mHasPerformedPurge && NetworkUtils.isNetworkAvailable(this)) { - mHasPerformedPurge = true; - ReaderDatabase.purgeAsync(); - } - if (!mHasPerformedInitialUpdate) { - performInitialUpdate(); - } - } - - @Override public void onSaveInstanceState(@Nonnull Bundle outState) { if (outState.isEmpty()) { outState.putBoolean("bug_19917_fix", true); @@ -170,60 +119,25 @@ public class ReaderPostListActivity extends WPDrawerActivity } @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_tags: - ReaderActivityLauncher.showReaderSubsForResult(this); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - boolean isResultOK = (resultCode == Activity.RESULT_OK); - final ReaderPostListFragment listFragment = getListFragment(); - switch (requestCode) { - // user just returned from the tags/subs activity - case ReaderConstants.INTENT_READER_SUBS : - if (data != null && listFragment != null) { - boolean tagsChanged = data.getBooleanExtra(ReaderSubsActivity.KEY_TAGS_CHANGED, false); - boolean blogsChanged = data.getBooleanExtra(ReaderSubsActivity.KEY_BLOGS_CHANGED, false); - // reload tags if they were changed, and set the last tag added as the current one - if (tagsChanged) { - String lastAddedTag = data.getStringExtra(ReaderSubsActivity.KEY_LAST_ADDED_TAG_NAME); - listFragment.doTagsChanged(lastAddedTag); - } - // refresh posts if blogs changed and user is viewing "Blogs I Follow" - if (blogsChanged - && listFragment.getPostListType() == ReaderTypes.ReaderPostListType.TAG_FOLLOWED - && ReaderTag.TAG_NAME_FOLLOWING.equals(listFragment.getCurrentTagName())) { - listFragment.refreshPosts(); - } - } - break; - - // user just returned from reblogging activity, reload the displayed post if reblogging - // succeeded - case ReaderConstants.INTENT_READER_REBLOG: - if (isResultOK && data != null && listFragment != null) { - long blogId = data.getLongExtra(ReaderConstants.ARG_BLOG_ID, 0); - long postId = data.getLongExtra(ReaderConstants.ARG_POST_ID, 0); - listFragment.reloadPost(ReaderPostTable.getPost(blogId, postId, true)); - } - break; - // user just returned from the login dialog, need to perform initial update again // since creds have changed case WPComLoginActivity.REQUEST_CODE: - if (isResultOK) { + if (resultCode == Activity.RESULT_OK) { removeListFragment(); - mHasPerformedInitialUpdate = false; - performInitialUpdate(); + EventBus.getDefault().removeStickyEvent(ReaderEvents.HasPerformedInitialUpdate.class); + } + break; + + // pass reader-related results to the fragment + case ReaderConstants.INTENT_READER_SUBS: + case ReaderConstants.INTENT_READER_REBLOG: + ReaderPostListFragment listFragment = getListFragment(); + if (listFragment != null) { + listFragment.onActivityResult(requestCode, resultCode, data); } break; } @@ -234,7 +148,7 @@ public class ReaderPostListActivity extends WPDrawerActivity super.onSignout(); AppLog.i(T.READER, "reader post list > user signed out"); - mHasPerformedInitialUpdate = false; + EventBus.getDefault().removeStickyEvent(ReaderEvents.HasPerformedInitialUpdate.class); // reader database will have been cleared by the time this is called, but the fragment must // be removed or else it will continue to show the same articles - onResume() will take @@ -242,10 +156,6 @@ public class ReaderPostListActivity extends WPDrawerActivity removeListFragment(); } - ReaderTypes.ReaderPostListType getPostListType() { - return (mPostListType != null ? mPostListType : ReaderTypes.DEFAULT_POST_LIST_TYPE); - } - private void removeListFragment() { Fragment listFragment = getListFragment(); if (listFragment != null) { @@ -303,88 +213,4 @@ public class ReaderPostListActivity extends WPDrawerActivity return ((ReaderPostListFragment) fragment); } - private boolean hasListFragment() { - return (getListFragment() != null); - } - - /* - * initial update performed the first time the user opens the reader - */ - private void performInitialUpdate() { - if (!NetworkUtils.isNetworkAvailable(this)) { - return; - } - - mHasPerformedInitialUpdate = true; - - // update current user to ensure we have their user_id as well as their latest info - // in case they changed their avatar, name, etc. since last time - AppLog.d(T.READER, "reader post list > updating current user"); - ReaderUserActions.updateCurrentUser(null); - - // update cookies so that we can show authenticated images in WebViews - AppLog.d(T.READER, "reader post list > updating cookies"); - ReaderAuthActions.updateCookies(ReaderPostListActivity.this); - } - - /* - * start background service to get the latest followed tags and blogs - */ - void updateFollowedTagsAndBlogs() { - AppLog.d(T.READER, "reader post list > updating tags and blogs"); - ReaderUpdateService.startService(this, - EnumSet.of(UpdateTask.TAGS, UpdateTask.FOLLOWED_BLOGS)); - } - - /* - * user tapped a post in the list fragment - */ - @Override - public void onPostSelected(long blogId, long postId) { - // skip if this activity no longer has the focus - this prevents the post detail from - // being shown multiple times if the user quickly taps a post more than once - if (!this.hasWindowFocus()) { - AppLog.w(T.READER, "reader post list > post selected when activity not focused"); - return; - } - - AnalyticsTracker.track(AnalyticsTracker.Stat.READER_OPENED_ARTICLE); - - ReaderPostListFragment listFragment = getListFragment(); - if (listFragment != null) { - switch (getPostListType()) { - case TAG_FOLLOWED: - case TAG_PREVIEW: - ReaderActivityLauncher.showReaderPostPagerForTag( - this, - listFragment.getCurrentTag(), - getPostListType(), - blogId, - postId); - break; - case BLOG_PREVIEW: - ReaderActivityLauncher.showReaderPostPagerForBlog( - this, - blogId, - postId); - break; - } - } - } - - /* - * user tapped a tag in the list fragment - */ - @Override - public void onTagSelected(String tagName) { - ReaderTag tag = new ReaderTag(tagName, ReaderTagType.FOLLOWED); - if (hasListFragment() && getListFragment().getPostListType().equals(ReaderTypes.ReaderPostListType.TAG_PREVIEW)) { - // user is already previewing a tag, so change current tag in existing preview - getListFragment().setCurrentTag(tag); - } else { - // user isn't previewing a tag, so open in tag preview - ReaderActivityLauncher.showReaderTagPreview(this, tag); - } - } - } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java index 7efd9a735..4a51be1ad 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -5,6 +5,7 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.app.Fragment; import android.content.Context; +import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -39,6 +40,7 @@ import com.cocosw.undobar.UndoBarController; import org.wordpress.android.R; import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.datasets.ReaderBlogTable; +import org.wordpress.android.datasets.ReaderDatabase; import org.wordpress.android.datasets.ReaderPostTable; import org.wordpress.android.datasets.ReaderTagTable; import org.wordpress.android.models.ReaderBlog; @@ -47,14 +49,16 @@ import org.wordpress.android.models.ReaderTag; import org.wordpress.android.models.ReaderTagType; import org.wordpress.android.ui.prefs.AppPrefs; import org.wordpress.android.ui.reader.ReaderTypes.ReaderPostListType; -import org.wordpress.android.ui.reader.ReaderTypes.RefreshType; import org.wordpress.android.ui.reader.actions.ReaderActions; import org.wordpress.android.ui.reader.actions.ReaderActions.RequestDataAction; +import org.wordpress.android.ui.reader.actions.ReaderAuthActions; import org.wordpress.android.ui.reader.actions.ReaderBlogActions; import org.wordpress.android.ui.reader.actions.ReaderPostActions; import org.wordpress.android.ui.reader.actions.ReaderTagActions; +import org.wordpress.android.ui.reader.actions.ReaderUserActions; import org.wordpress.android.ui.reader.adapters.ReaderPostAdapter; import org.wordpress.android.ui.reader.adapters.ReaderTagSpinnerAdapter; +import org.wordpress.android.ui.reader.services.ReaderUpdateService; import org.wordpress.android.ui.reader.views.ReaderBlogInfoView; import org.wordpress.android.ui.reader.views.ReaderFollowButton; import org.wordpress.android.ui.reader.views.ReaderRecyclerView; @@ -70,13 +74,17 @@ import org.wordpress.android.util.ptr.SwipeToRefreshHelper; import org.wordpress.android.util.ptr.SwipeToRefreshHelper.RefreshListener; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Stack; import de.greenrobot.event.EventBus; -public class ReaderPostListFragment extends Fragment { +public class ReaderPostListFragment extends Fragment + implements ReaderInterfaces.OnPostSelectedListener, + ReaderInterfaces.OnTagSelectedListener, + ReaderInterfaces.OnPostPopupListener { private Spinner mSpinner; private ReaderTagSpinnerAdapter mSpinnerAdapter; @@ -84,9 +92,6 @@ public class ReaderPostListFragment extends Fragment { private ReaderPostAdapter mPostAdapter; private ReaderRecyclerView mRecyclerView; - private ReaderInterfaces.OnPostSelectedListener mPostSelectedListener; - private ReaderInterfaces.OnTagSelectedListener mOnTagSelectedListener; - private SwipeToRefreshHelper mSwipeToRefreshHelper; private View mNewPostsBar; private View mEmptyView; @@ -249,7 +254,7 @@ public class ReaderPostListFragment extends Fragment { && getPostListType() == ReaderPostListType.TAG_FOLLOWED && ReaderTagTable.shouldAutoUpdateTag(mCurrentTag)) { AppLog.i(T.READER, "reader post list > auto-updating current tag after resume"); - updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_NEWER, RefreshType.AUTOMATIC); + updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_NEWER); } } } @@ -257,7 +262,14 @@ public class ReaderPostListFragment extends Fragment { @Override public void onStart() { super.onStart(); + EventBus.getDefault().register(this); + + purgeDatabaseIfNeeded(); + performInitialUpdateIfNeeded(); + if (getPostListType() == ReaderPostListType.TAG_FOLLOWED) { + updateFollowedTagsAndBlogsIfNeeded(); + } } @Override @@ -283,7 +295,8 @@ public class ReaderPostListFragment extends Fragment { public void onEventMainThread(ReaderEvents.FollowedBlogsChanged event) { // refresh posts if user is viewing "Blogs I Follow" if (getPostListType() == ReaderTypes.ReaderPostListType.TAG_FOLLOWED - && ReaderTag.TAG_NAME_FOLLOWING.equals(getCurrentTagName())) { + && hasCurrentTag() + && getCurrentTag().isBlogsIFollow()) { refreshPosts(); } } @@ -378,7 +391,7 @@ public class ReaderPostListFragment extends Fragment { switch (getPostListType()) { case TAG_FOLLOWED: case TAG_PREVIEW: - updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_NEWER, RefreshType.MANUAL); + updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_NEWER); break; case BLOG_PREVIEW: updatePostsInCurrentBlogOrFeed(RequestDataAction.LOAD_NEWER); @@ -393,18 +406,6 @@ public class ReaderPostListFragment extends Fragment { return rootView; } - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - if (activity instanceof ReaderInterfaces.OnPostSelectedListener) { - mPostSelectedListener = (ReaderInterfaces.OnPostSelectedListener) activity; - } - if (activity instanceof ReaderInterfaces.OnTagSelectedListener) { - mOnTagSelectedListener = (ReaderInterfaces.OnTagSelectedListener) activity; - } - } - /* * animate in the blog/tag info header after a brief delay */ @@ -467,7 +468,7 @@ public class ReaderPostListFragment extends Fragment { boolean isRecreated = (savedInstanceState != null); getPostAdapter().setCurrentTag(mCurrentTag); if (!isRecreated && ReaderTagTable.shouldAutoUpdateTag(mCurrentTag)) { - updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_NEWER, RefreshType.AUTOMATIC); + updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_NEWER); } } @@ -485,9 +486,6 @@ public class ReaderPostListFragment extends Fragment { animateHeaderDelayed(); break; } - - getPostAdapter().setOnTagSelectedListener(mOnTagSelectedListener); - getPostAdapter().setOnPostPopupListener(mOnPostPopupListener); } /* @@ -556,41 +554,29 @@ public class ReaderPostListFragment extends Fragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); + // only followed tag list has a menu if (getPostListType() == ReaderPostListType.TAG_FOLLOWED) { - inflater.inflate(R.menu.reader_native, menu); + inflater.inflate(R.menu.reader_list, menu); setupActionBar(); } } - /* - * called when user taps dropdown arrow icon next to a post - shows a popup menu - * that enables blocking the blog the post is in - */ - private final ReaderInterfaces.OnPostPopupListener mOnPostPopupListener = new ReaderInterfaces.OnPostPopupListener() { - @Override - public void onShowPostPopup(View view, final ReaderPost post) { - if (view == null || post == null) { - return; - } - - PopupMenu popup = new PopupMenu(getActivity(), view); - MenuItem menuItem = popup.getMenu().add(getString(R.string.reader_menu_block_blog)); - menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - blockBlogForPost(post); - return true; - } - }); - popup.show(); + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_tags: + ReaderActivityLauncher.showReaderSubsForResult(getActivity()); + return true; + default: + return super.onOptionsItemSelected(item); } - }; + } /* - * blocks the blog associated with the passed post and removes all posts in that blog - * from the adapter - */ + * blocks the blog associated with the passed post and removes all posts in that blog + * from the adapter + */ private void blockBlogForPost(final ReaderPost post) { if (post == null || !hasPostAdapter()) { return; @@ -688,11 +674,11 @@ public class ReaderPostListFragment extends Fragment { Map<String, String> properties = new HashMap<>(); properties.put("tag", tag.getTagName()); AnalyticsTracker.track(AnalyticsTracker.Stat.READER_LOADED_TAG, properties); - if (tag.getTagName().equals(ReaderTag.TAG_NAME_FRESHLY_PRESSED)) { + if (tag.isFreshlyPressed()) { AnalyticsTracker.track(AnalyticsTracker.Stat.READER_LOADED_FRESHLY_PRESSED); } } - setCurrentTag(tag); + setCurrentTag(tag, true); AppLog.d(T.READER, String.format("reader post list > tag %s displayed", tag.getTagNameForLog())); } @@ -739,26 +725,14 @@ public class ReaderPostListFragment extends Fragment { titleResId = R.string.reader_empty_posts_in_tag_updating; } else if (getPostListType() == ReaderPostListType.BLOG_PREVIEW) { titleResId = R.string.reader_empty_posts_in_blog; - } else if (getPostListType() == ReaderPostListType.TAG_FOLLOWED && getSpinnerAdapter() != null) { - int tagIndex = getSpinnerAdapter().getIndexOfTag(mCurrentTag); - String tagId; - if (tagIndex > -1) { - ReaderTag tag = (ReaderTag) getSpinnerAdapter().getItem(tagIndex); - tagId = tag.getStringIdFromEndpoint(); + } else if (getPostListType() == ReaderPostListType.TAG_FOLLOWED && hasCurrentTag()) { + if (getCurrentTag().isBlogsIFollow()) { + titleResId = R.string.reader_empty_followed_blogs_title; + descriptionResId = R.string.reader_empty_followed_blogs_description; + } else if (getCurrentTag().isPostsILike()) { + titleResId = R.string.reader_empty_posts_liked; } else { - tagId = ""; - } - switch (tagId) { - case ReaderTag.TAG_ID_FOLLOWING: - titleResId = R.string.reader_empty_followed_blogs_title; - descriptionResId = R.string.reader_empty_followed_blogs_description; - break; - case ReaderTag.TAG_ID_LIKED: - titleResId = R.string.reader_empty_posts_liked; - break; - default: - titleResId = R.string.reader_empty_posts_in_tag; - break; + titleResId = R.string.reader_empty_posts_in_tag; } } else { titleResId = R.string.reader_empty_posts_in_tag; @@ -818,7 +792,7 @@ public class ReaderPostListFragment extends Fragment { case TAG_PREVIEW: if (ReaderPostTable.getNumPostsWithTag(mCurrentTag) < ReaderConstants.READER_MAX_POSTS_TO_DISPLAY) { // request older posts - updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_OLDER, RefreshType.MANUAL); + updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_OLDER); AnalyticsTracker.track(AnalyticsTracker.Stat.READER_INFINITE_SCROLL); } break; @@ -856,7 +830,9 @@ public class ReaderPostListFragment extends Fragment { AppLog.d(T.READER, "reader post list > creating post adapter"); Context context = WPActivityUtils.getThemedContext(getActivity()); mPostAdapter = new ReaderPostAdapter(context, getPostListType()); - mPostAdapter.setOnPostSelectedListener(mPostSelectedListener); + mPostAdapter.setOnPostSelectedListener(this); + mPostAdapter.setOnTagSelectedListener(this); + mPostAdapter.setOnPostPopupListener(this); mPostAdapter.setOnDataLoadedListener(mDataLoadedListener); mPostAdapter.setOnDataRequestedListener(mDataRequestedListener); mPostAdapter.setOnReblogRequestedListener(mRequestReblogListener); @@ -887,22 +863,10 @@ public class ReaderPostListFragment extends Fragment { return (mCurrentTag != null ? mCurrentTag.getTagName() : ""); } - private boolean hasCurrentTag() { + boolean hasCurrentTag() { return mCurrentTag != null; } - void setCurrentTagName(String tagName) { - setCurrentTagName(tagName, true); - } - void setCurrentTagName(String tagName, boolean allowAutoUpdate) { - if (TextUtils.isEmpty(tagName)) { - return; - } - setCurrentTag(new ReaderTag(tagName, ReaderTagType.FOLLOWED), allowAutoUpdate); - } - void setCurrentTag(final ReaderTag tag) { - setCurrentTag(tag, true); - } void setCurrentTag(final ReaderTag tag, boolean allowAutoUpdate) { if (tag == null) { return; @@ -941,7 +905,7 @@ public class ReaderPostListFragment extends Fragment { // update posts in this tag if it's time to do so if (allowAutoUpdate && ReaderTagTable.shouldAutoUpdateTag(tag)) { - updatePostsWithTag(tag, RequestDataAction.LOAD_NEWER, RefreshType.AUTOMATIC); + updatePostsWithTag(tag, RequestDataAction.LOAD_NEWER); } } @@ -955,15 +919,15 @@ public class ReaderPostListFragment extends Fragment { return false; } - String tag = mTagPreviewHistory.pop(); - if (isCurrentTagName(tag)) { + String tagName = mTagPreviewHistory.pop(); + if (isCurrentTagName(tagName)) { if (mTagPreviewHistory.empty()) { return false; } - tag = mTagPreviewHistory.pop(); + tagName = mTagPreviewHistory.pop(); } - setCurrentTagName(tag, false); + setCurrentTag(new ReaderTag(tagName, ReaderTagType.FOLLOWED), false); updateFollowButton(); return true; @@ -1038,15 +1002,14 @@ public class ReaderPostListFragment extends Fragment { } void updateCurrentTag() { - updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_NEWER, RefreshType.AUTOMATIC); + updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_NEWER); } /* * get latest posts for this tag from the server */ void updatePostsWithTag(final ReaderTag tag, - final RequestDataAction updateAction, - final RefreshType refreshType) { + final RequestDataAction updateAction) { if (tag == null) { return; } @@ -1069,13 +1032,6 @@ public class ReaderPostListFragment extends Fragment { return; } - // if this is "Posts I Like" or "Blogs I Follow" and it's a manual refresh (user tapped refresh icon), - // refresh the posts so posts that were unliked/unfollowed no longer appear - if (refreshType == RefreshType.MANUAL && isCurrentTag(tag)) { - if (tag.getTagName().equals(ReaderTag.TAG_NAME_LIKED) || tag.getTagName().equals(ReaderTag.TAG_NAME_FOLLOWING)) - refreshPosts(); - } - ReaderActions.UpdateResultListener resultListener = new ReaderActions.UpdateResultListener() { @Override public void onUpdateResult(ReaderActions.UpdateResult result) { @@ -1102,6 +1058,12 @@ public class ReaderPostListFragment extends Fragment { } else { boolean requestFailed = (result == ReaderActions.UpdateResult.FAILED); setEmptyTitleAndDescription(requestFailed); + // refresh if this is "Posts I Like" or "Blogs I Follow" so posts that + // were unliked/unfollowed no longer appear + if (updateAction == RequestDataAction.LOAD_NEWER + && (tag.isPostsILike() || tag.isBlogsIFollow())) { + refreshPosts(); + } } } }; @@ -1227,7 +1189,7 @@ public class ReaderPostListFragment extends Fragment { checkCurrentTag(); getSpinnerAdapter().reloadTags(); if (!TextUtils.isEmpty(newCurrentTag)) { - setCurrentTagName(newCurrentTag); + setCurrentTag(new ReaderTag(newCurrentTag, ReaderTagType.FOLLOWED), true); } } @@ -1359,4 +1321,159 @@ public class ReaderPostListFragment extends Fragment { ReaderTagActions.TagAction action = (isAskingToFollow ? ReaderTagActions.TagAction.ADD : ReaderTagActions.TagAction.DELETE); ReaderTagActions.performTagAction(getCurrentTag(), action, null); } + + /* + * called from adapter when user taps a post + */ + @Override + public void onPostSelected(long blogId, long postId) { + if (!isAdded()) return; + + AnalyticsTracker.track(AnalyticsTracker.Stat.READER_OPENED_ARTICLE); + + switch (getPostListType()) { + case TAG_FOLLOWED: + case TAG_PREVIEW: + ReaderActivityLauncher.showReaderPostPagerForTag( + getActivity(), + getCurrentTag(), + getPostListType(), + blogId, + postId); + break; + case BLOG_PREVIEW: + ReaderActivityLauncher.showReaderPostPagerForBlog( + getActivity(), + blogId, + postId); + break; + } + } + + /* + * called from adapter when user taps a tag on a post + */ + @Override + public void onTagSelected(String tagName) { + if (!isAdded()) return; + + ReaderTag tag = new ReaderTag(tagName, ReaderTagType.FOLLOWED); + if (getPostListType().equals(ReaderTypes.ReaderPostListType.TAG_PREVIEW)) { + // user is already previewing a tag, so change current tag in existing preview + setCurrentTag(tag, true); + } else { + // user isn't previewing a tag, so open in tag preview + ReaderActivityLauncher.showReaderTagPreview(getActivity(), tag); + } + } + + /* + * called when user taps dropdown arrow icon next to a post - shows a popup menu + * that enables blocking the blog the post is in + */ + @Override + public void onShowPostPopup(View view, final ReaderPost post) { + if (view == null || post == null || !isAdded()) { + return; + } + + PopupMenu popup = new PopupMenu(getActivity(), view); + MenuItem menuItem = popup.getMenu().add(getString(R.string.reader_menu_block_blog)); + menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + blockBlogForPost(post); + return true; + } + }); + popup.show(); + } + + /* + * called from activity to handle reader-related onActivityResult + */ + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + // user just returned from the tags/subs activity + case ReaderConstants.INTENT_READER_SUBS: + if (data != null) { + boolean tagsChanged = data.getBooleanExtra(ReaderSubsActivity.KEY_TAGS_CHANGED, false); + boolean blogsChanged = data.getBooleanExtra(ReaderSubsActivity.KEY_BLOGS_CHANGED, false); + // reload tags if they were changed, and set the last tag added as the current one + if (tagsChanged) { + String lastAddedTag = data.getStringExtra(ReaderSubsActivity.KEY_LAST_ADDED_TAG_NAME); + doTagsChanged(lastAddedTag); + } + // refresh posts if blogs changed and user is viewing "Blogs I Follow" + if (blogsChanged + && getPostListType() == ReaderTypes.ReaderPostListType.TAG_FOLLOWED + && hasCurrentTag() + && getCurrentTag().isBlogsIFollow()) { + refreshPosts(); + } + } + break; + + // user just returned from reblogging activity, reload the displayed post if reblogging + // succeeded + case ReaderConstants.INTENT_READER_REBLOG: + if (resultCode == Activity.RESULT_OK && data != null) { + long blogId = data.getLongExtra(ReaderConstants.ARG_BLOG_ID, 0); + long postId = data.getLongExtra(ReaderConstants.ARG_POST_ID, 0); + reloadPost(ReaderPostTable.getPost(blogId, postId, true)); + } + break; + } + } + + /* + * purge reader db if it hasn't been done yet, but only if there's an active connection + * since we don't want to purge posts that the user would expect to see when offline + */ + private void purgeDatabaseIfNeeded() { + if (EventBus.getDefault().getStickyEvent(ReaderEvents.HasPurgedDatabase.class) == null + && NetworkUtils.isNetworkAvailable(getActivity())) { + AppLog.d(T.READER, "reader post list > purging database"); + ReaderDatabase.purgeAsync(); + EventBus.getDefault().postSticky(new ReaderEvents.HasPurgedDatabase()); + } + } + + /* + * initial update performed the first time the user opens the reader + */ + private void performInitialUpdateIfNeeded() { + if (EventBus.getDefault().getStickyEvent(ReaderEvents.HasPerformedInitialUpdate.class) == null + && NetworkUtils.isNetworkAvailable(getActivity())) { + // update current user to ensure we have their user_id as well as their latest info + // in case they changed their avatar, name, etc. since last time + AppLog.d(T.READER, "reader post list > updating current user"); + ReaderUserActions.updateCurrentUser(); + + // update cookies so that we can show authenticated images in WebViews + AppLog.d(T.READER, "reader post list > updating cookies"); + ReaderAuthActions.updateCookies(getActivity()); + + EventBus.getDefault().postSticky(new ReaderEvents.HasPerformedInitialUpdate()); + } + } + + /* + * start background service to get the latest followed tags and blogs if it's time to do so + */ + void updateFollowedTagsAndBlogsIfNeeded() { + ReaderEvents.UpdatedFollowedTagsAndBlogs lastUpdateEvent = + EventBus.getDefault().getStickyEvent(ReaderEvents.UpdatedFollowedTagsAndBlogs.class); + if (lastUpdateEvent != null && lastUpdateEvent.minutesSinceLastUpdate() < 120) { + return; + } + + AppLog.d(T.READER, "reader post list > updating tags and blogs"); + EventBus.getDefault().postSticky(new ReaderEvents.UpdatedFollowedTagsAndBlogs()); + + ReaderUpdateService.startService(getActivity(), + EnumSet.of(ReaderUpdateService.UpdateTask.TAGS, + ReaderUpdateService.UpdateTask.FOLLOWED_BLOGS)); + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderTypes.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderTypes.java index 307f94836..74718bddd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderTypes.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderTypes.java @@ -18,9 +18,4 @@ public class ReaderTypes { return this.equals(TAG_PREVIEW) || this.equals(BLOG_PREVIEW); } } - - protected static enum RefreshType { - AUTOMATIC, // refresh was performed by the app without user requesting it - MANUAL // refresh was requested by the user - } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java index 28e32eeb1..38837acbe 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java @@ -54,7 +54,7 @@ public class ReaderBlogActions { } final String actionName = (isAskingToFollow ? "follow" : "unfollow"); - final String path = "/sites/" + blogId + "/follows/" + (isAskingToFollow ? "new" : "mine/delete"); + final String path = "sites/" + blogId + "/follows/" + (isAskingToFollow ? "new" : "mine/delete"); com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() { @Override @@ -82,7 +82,7 @@ public class ReaderBlogActions { } } }; - WordPress.getRestClientUtils().post(path, listener, errorListener); + WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener); return true; } @@ -170,7 +170,7 @@ public class ReaderBlogActions { } final String actionName = (isAskingToFollow ? "follow" : "unfollow"); - final String path = "/read/following/mine/" + final String path = "read/following/mine/" + (isAskingToFollow ? "new" : "delete") + "?url=" + UrlUtils.urlEncode(feedUrl); @@ -200,7 +200,7 @@ public class ReaderBlogActions { } } }; - WordPress.getRestClientUtils().post(path, listener, errorListener); + WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener); return true; } @@ -307,9 +307,9 @@ public class ReaderBlogActions { }; if (hasBlogId) { - WordPress.getRestClientUtilsV1_1().get("/sites/" + blogId, listener, errorListener); + WordPress.getRestClientUtilsV1_1().get("sites/" + blogId, listener, errorListener); } else { - WordPress.getRestClientUtilsV1_1().get("/sites/" + UrlUtils.urlEncode(UrlUtils.getDomainFromUrl(blogUrl)), listener, errorListener); + WordPress.getRestClientUtilsV1_1().get("sites/" + UrlUtils.urlEncode(UrlUtils.getDomainFromUrl(blogUrl)), listener, errorListener); } } public static void updateFeedInfo(long feedId, String feedUrl, final UpdateBlogInfoListener infoListener) { @@ -330,9 +330,9 @@ public class ReaderBlogActions { }; String path; if (feedId != 0) { - path = "/read/feed/" + feedId; + path = "read/feed/" + feedId; } else { - path = "/read/feed/" + UrlUtils.urlEncode(feedUrl); + path = "read/feed/" + UrlUtils.urlEncode(feedUrl); } WordPress.getRestClientUtilsV1_1().get(path, listener, errorListener); } @@ -431,8 +431,8 @@ public class ReaderBlogActions { }; AppLog.i(T.READER, "blocking blog " + blogId); - String path = "/me/block/sites/" + Long.toString(blogId) + "/new"; - WordPress.getRestClientUtils().post(path, listener, errorListener); + String path = "me/block/sites/" + Long.toString(blogId) + "/new"; + WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener); return blockResult; } @@ -466,7 +466,7 @@ public class ReaderBlogActions { }; AppLog.i(T.READER, "unblocking blog " + blockResult.blogId); - String path = "/me/block/sites/" + Long.toString(blockResult.blogId) + "/delete"; - WordPress.getRestClientUtils().post(path, listener, errorListener); + String path = "me/block/sites/" + Long.toString(blockResult.blogId) + "/delete"; + WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderCommentActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderCommentActions.java index 56d0fe7e2..2821b4ff2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderCommentActions.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderCommentActions.java @@ -58,7 +58,7 @@ public class ReaderCommentActions { } }; AppLog.d(T.READER, "updating comments"); - WordPress.getRestClientUtils().get(path, null, null, listener, errorListener); + WordPress.getRestClientUtilsV1_1().get(path, null, null, listener, errorListener); } private static void handleUpdateCommentsResponse(final JSONObject jsonObject, final long blogId, @@ -212,7 +212,7 @@ public class ReaderCommentActions { }; AppLog.i(T.READER, "submitting comment"); - WordPress.getRestClientUtils().post(path, params, null, listener, errorListener); + WordPress.getRestClientUtilsV1_1().post(path, params, null, listener, errorListener); return newComment; } @@ -275,7 +275,7 @@ public class ReaderCommentActions { } }; - WordPress.getRestClientUtils().post(path, listener, errorListener); + WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener); return true; } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderPostActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderPostActions.java index a88285b17..7088f863c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderPostActions.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderPostActions.java @@ -27,6 +27,7 @@ import org.wordpress.android.ui.reader.utils.ReaderUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.JSONUtil; +import org.wordpress.android.util.StringUtils; import org.wordpress.android.util.UrlUtils; import org.wordpress.android.util.VolleyUtils; @@ -86,7 +87,7 @@ public class ReaderPostActions { } }; - WordPress.getRestClientUtils().post(path, listener, errorListener); + WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener); return true; } @@ -134,10 +135,10 @@ public class ReaderPostActions { } }; - String path = "/sites/" + post.blogId + String path = "sites/" + post.blogId + "/posts/" + post.postId + "/reblogs/new"; - WordPress.getRestClientUtils().post(path, params, null, listener, errorListener); + WordPress.getRestClientUtilsV1_1().post(path, params, null, listener, errorListener); } /* @@ -164,7 +165,7 @@ public class ReaderPostActions { } }; AppLog.d(T.READER, "updating post"); - WordPress.getRestClientUtils().get(path, null, null, listener, errorListener); + WordPress.getRestClientUtilsV1_1().get(path, null, null, listener, errorListener); } private static void handleUpdatePostResponse(final ReaderPost originalPost, @@ -284,7 +285,7 @@ public class ReaderPostActions { } }; AppLog.d(T.READER, "requesting post"); - WordPress.getRestClientUtils().get(path, null, null, listener, errorListener); + WordPress.getRestClientUtilsV1_1().get(path, null, null, listener, errorListener); } /* @@ -338,7 +339,7 @@ public class ReaderPostActions { } }; - WordPress.getRestClientUtils().get(sb.toString(), null, null, listener, errorListener); + WordPress.getRestClientUtilsV1_1().get(sb.toString(), null, null, listener, errorListener); } /* @@ -372,13 +373,13 @@ public class ReaderPostActions { } }; AppLog.d(T.READER, "updating posts in blog " + blogId); - WordPress.getRestClientUtils().get(path, null, null, listener, errorListener); + WordPress.getRestClientUtilsV1_1().get(path, null, null, listener, errorListener); } public static void requestPostsForFeed(final long feedId, final RequestDataAction updateAction, final UpdateResultListener resultListener) { - String path = "/read/feed/" + feedId + "/posts/?meta=site,likes"; + String path = "read/feed/" + feedId + "/posts/?meta=site,likes"; if (updateAction == RequestDataAction.LOAD_OLDER) { String dateOldest = ReaderPostTable.getOldestPubDateInFeed(feedId); if (!TextUtils.isEmpty(dateOldest)) { @@ -452,13 +453,13 @@ public class ReaderPostActions { // if passed tag has an assigned endpoint, return it and be done if (!TextUtils.isEmpty(tag.getEndpoint())) { - return tag.getEndpoint(); + return getRelativeEndpoint(tag.getEndpoint()); } // check the db for the endpoint String endpoint = ReaderTagTable.getEndpointForTag(tag); if (!TextUtils.isEmpty(endpoint)) { - return endpoint; + return getRelativeEndpoint(endpoint); } // never hand craft the endpoint for default tags, since these MUST be updated @@ -467,7 +468,30 @@ public class ReaderPostActions { return null; } - return String.format("/read/tags/%s/posts", ReaderUtils.sanitizeWithDashes(tag.getTagName())); + return String.format("read/tags/%s/posts", ReaderUtils.sanitizeWithDashes(tag.getTagName())); + } + + /* + * returns the passed endpoint without the unnecessary path - this is + * needed because as of 20-Feb-2015 the /read/menu/ call returns the + * full path but we don't want to use the full path since it may change + * between API versions (as it did when we moved from v1 to v1.1) + * + * ex: https://public-api.wordpress.com/rest/v1/read/tags/fitness/posts + * becomes just read/tags/fitness/posts + */ + private static String getRelativeEndpoint(final String endpoint) { + if (endpoint != null && endpoint.startsWith("http")) { + int pos = endpoint.indexOf("/read/"); + if (pos > -1) { + return endpoint.substring(pos + 1, endpoint.length()); + } + pos = endpoint.indexOf("/v1/"); + if (pos > -1) { + return endpoint.substring(pos + 4, endpoint.length()); + } + } + return StringUtils.notNullStr(endpoint); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderTagActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderTagActions.java index 5a204cd74..22f9c8aad 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderTagActions.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderTagActions.java @@ -112,7 +112,7 @@ public class ReaderTagActions { } } }; - WordPress.getRestClientUtils().post(path, listener, errorListener); + WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener); return true; } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderUserActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderUserActions.java index d82c8979d..93cf20fcf 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderUserActions.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderUserActions.java @@ -15,28 +15,17 @@ public class ReaderUserActions { /* * request the current user's info, update locally if different than existing local */ - public static void updateCurrentUser(final ReaderActions.UpdateResultListener resultListener) { + public static void updateCurrentUser() { com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() { @Override public void onResponse(JSONObject jsonObject) { - final ReaderActions.UpdateResult result; - if (jsonObject == null) { - result = ReaderActions.UpdateResult.FAILED; - } else { + if (jsonObject != null) { final ReaderUser serverUser = ReaderUser.fromJson(jsonObject); final ReaderUser localUser = ReaderUserTable.getCurrentUser(); - if (serverUser == null) { - result = ReaderActions.UpdateResult.FAILED; - } else if (serverUser.isSameUser(localUser)) { - result = ReaderActions.UpdateResult.UNCHANGED; - } else { + if (serverUser != null && !serverUser.isSameUser(localUser)) { setCurrentUser(serverUser); - result = ReaderActions.UpdateResult.CHANGED; } } - - if (resultListener != null) - resultListener.onUpdateResult(result); } }; @@ -44,12 +33,10 @@ public class ReaderUserActions { @Override public void onErrorResponse(VolleyError volleyError) { AppLog.e(T.READER, volleyError); - if (resultListener != null) - resultListener.onUpdateResult(ReaderActions.UpdateResult.FAILED); } }; - WordPress.getRestClientUtils().get("me", listener, errorListener); + WordPress.getRestClientUtilsV1_1().get("me", listener, errorListener); } /* diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/services/ReaderUpdateService.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/services/ReaderUpdateService.java index b6c0f62e2..c7ca3548e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/services/ReaderUpdateService.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/services/ReaderUpdateService.java @@ -130,7 +130,7 @@ public class ReaderUpdateService extends Service { } }; AppLog.d(AppLog.T.READER, "reader service > updating tags"); - WordPress.getRestClientUtils().get("read/menu", null, null, listener, errorListener); + WordPress.getRestClientUtilsV1_1().get("read/menu", null, null, listener, errorListener); } private void handleUpdateTagsResponse(final JSONObject jsonObject) { @@ -240,7 +240,7 @@ public class ReaderUpdateService extends Service { AppLog.d(AppLog.T.READER, "reader service > updating followed blogs"); // request using ?meta=site,feed to get extra info - WordPress.getRestClientUtils().get("/read/following/mine?meta=site%2Cfeed", listener, errorListener); + WordPress.getRestClientUtilsV1_1().get("read/following/mine?meta=site%2Cfeed", listener, errorListener); } private void handleFollowedBlogsResponse(final JSONObject jsonObject) { new Thread() { @@ -279,10 +279,10 @@ public class ReaderUpdateService extends Service { }; AppLog.d(AppLog.T.READER, "reader service > updating recommended blogs"); - String path = "/read/recommendations/mine/" - + "?source=mobile" - + "&number=" + Integer.toString(ReaderConstants.READER_MAX_RECOMMENDED_TO_REQUEST); - WordPress.getRestClientUtils().get(path, listener, errorListener); + String path = "read/recommendations/mine/" + + "?source=mobile" + + "&number=" + Integer.toString(ReaderConstants.READER_MAX_RECOMMENDED_TO_REQUEST); + WordPress.getRestClientUtilsV1_1().get(path, listener, errorListener); } private void handleRecommendedBlogsResponse(final JSONObject jsonObject) { new Thread() { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/StatsReferrersFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/stats/StatsReferrersFragment.java index 62cf4271b..a3761cc8d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/StatsReferrersFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/StatsReferrersFragment.java @@ -205,7 +205,6 @@ public class StatsReferrersFragment extends StatsAbstractListFragment { WPNetworkImageView.ImageType.BLAVATAR); holder.networkImageView.setVisibility(View.VISIBLE); - holder.chevronImageView.setVisibility(View.VISIBLE); if (children == 0) { holder.showLinkIcon(); } else { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/StatsUIHelper.java b/WordPress/src/main/java/org/wordpress/android/ui/stats/StatsUIHelper.java index d358a75a0..367c8a5fc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/StatsUIHelper.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/StatsUIHelper.java @@ -157,10 +157,16 @@ public class StatsUIHelper { mLinearLayout.addView(groupView); } + // groupView is recycled, we need to reset it to the original state. + ViewGroup childContainer = (ViewGroup) groupView.findViewById(R.id.layout_child_container); + if (childContainer != null) { + childContainer.setVisibility(View.GONE); + } // Remove any other prev animations set on the chevron final ImageView chevron = (ImageView) groupView.findViewById(R.id.stats_list_cell_chevron); if (chevron != null) { chevron.clearAnimation(); + chevron.setImageResource(R.drawable.stats_chevron_right); } // add children if this group is expanded |