aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java')
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java1171
1 files changed, 1171 insertions, 0 deletions
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
new file mode 100644
index 000000000..da635bfe4
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java
@@ -0,0 +1,1171 @@
+package org.wordpress.android.ui.reader;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.analytics.AnalyticsTracker;
+import org.wordpress.android.datasets.ReaderLikeTable;
+import org.wordpress.android.datasets.ReaderPostTable;
+import org.wordpress.android.models.ReaderPost;
+import org.wordpress.android.models.ReaderPostDiscoverData;
+import org.wordpress.android.models.ReaderTag;
+import org.wordpress.android.models.ReaderTagType;
+import org.wordpress.android.ui.main.WPMainActivity;
+import org.wordpress.android.ui.reader.ReaderActivityLauncher.OpenUrlType;
+import org.wordpress.android.ui.reader.ReaderActivityLauncher.PhotoViewerOption;
+import org.wordpress.android.ui.reader.ReaderInterfaces.AutoHideToolbarListener;
+import org.wordpress.android.ui.reader.ReaderTypes.ReaderPostListType;
+import org.wordpress.android.ui.reader.actions.ReaderActions;
+import org.wordpress.android.ui.reader.actions.ReaderBlogActions;
+import org.wordpress.android.ui.reader.actions.ReaderPostActions;
+import org.wordpress.android.ui.reader.models.ReaderBlogIdPostId;
+import org.wordpress.android.ui.reader.models.ReaderRelatedPost;
+import org.wordpress.android.ui.reader.models.ReaderRelatedPostList;
+import org.wordpress.android.ui.reader.utils.ReaderUtils;
+import org.wordpress.android.ui.reader.utils.ReaderVideoUtils;
+import org.wordpress.android.ui.reader.views.ReaderFollowButton;
+import org.wordpress.android.ui.reader.views.ReaderIconCountView;
+import org.wordpress.android.ui.reader.views.ReaderLikingUsersView;
+import org.wordpress.android.ui.reader.views.ReaderWebView;
+import org.wordpress.android.ui.reader.views.ReaderWebView.ReaderCustomViewListener;
+import org.wordpress.android.ui.reader.views.ReaderWebView.ReaderWebViewPageFinishedListener;
+import org.wordpress.android.ui.reader.views.ReaderWebView.ReaderWebViewUrlClickListener;
+import org.wordpress.android.util.AnalyticsUtils;
+import org.wordpress.android.util.AniUtils;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+import org.wordpress.android.util.DateTimeUtils;
+import org.wordpress.android.util.DisplayUtils;
+import org.wordpress.android.util.GravatarUtils;
+import org.wordpress.android.util.NetworkUtils;
+import org.wordpress.android.util.PhotonUtils;
+import org.wordpress.android.util.ToastUtils;
+import org.wordpress.android.util.UrlUtils;
+import org.wordpress.android.util.helpers.SwipeToRefreshHelper;
+import org.wordpress.android.util.widgets.CustomSwipeRefreshLayout;
+import org.wordpress.android.widgets.WPNetworkImageView;
+import org.wordpress.android.widgets.WPScrollView;
+import org.wordpress.android.widgets.WPScrollView.ScrollDirectionListener;
+
+import java.util.EnumSet;
+
+import de.greenrobot.event.EventBus;
+
+public class ReaderPostDetailFragment extends Fragment
+ implements WPMainActivity.OnActivityBackPressedListener,
+ ScrollDirectionListener,
+ ReaderCustomViewListener,
+ ReaderWebViewPageFinishedListener,
+ ReaderWebViewUrlClickListener {
+
+ private long mPostId;
+ private long mBlogId;
+ private ReaderPost mPost;
+ private ReaderPostRenderer mRenderer;
+ private ReaderPostListType mPostListType;
+
+ private final ReaderPostHistory mPostHistory = new ReaderPostHistory();
+
+ private SwipeToRefreshHelper mSwipeToRefreshHelper;
+ private WPScrollView mScrollView;
+ private ViewGroup mLayoutFooter;
+ private ReaderWebView mReaderWebView;
+ private ReaderLikingUsersView mLikingUsersView;
+ private View mLikingUsersDivider;
+ private View mLikingUsersLabel;
+
+ private boolean mHasAlreadyUpdatedPost;
+ private boolean mHasAlreadyRequestedPost;
+ private boolean mIsLoggedOutReader;
+ private boolean mIsWebViewPaused;
+ private boolean mIsRelatedPost;
+
+ private int mToolbarHeight;
+ private String mErrorMessage;
+
+ private boolean mIsToolbarShowing = true;
+ private AutoHideToolbarListener mAutoHideToolbarListener;
+
+ // min scroll distance before toggling toolbar
+ private static final float MIN_SCROLL_DISTANCE_Y = 10;
+
+ public static ReaderPostDetailFragment newInstance(long blogId, long postId) {
+ return newInstance(blogId, postId, false, null);
+ }
+
+ public static ReaderPostDetailFragment newInstance(long blogId,
+ long postId,
+ boolean isRelatedPost,
+ ReaderPostListType postListType) {
+ AppLog.d(T.READER, "reader post detail > newInstance");
+
+ Bundle args = new Bundle();
+ args.putLong(ReaderConstants.ARG_BLOG_ID, blogId);
+ args.putLong(ReaderConstants.ARG_POST_ID, postId);
+ args.putBoolean(ReaderConstants.ARG_IS_RELATED_POST, isRelatedPost);
+ if (postListType != null) {
+ args.putSerializable(ReaderConstants.ARG_POST_LIST_TYPE, postListType);
+ }
+
+ ReaderPostDetailFragment fragment = new ReaderPostDetailFragment();
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mIsLoggedOutReader = ReaderUtils.isLoggedOutReader();
+ if (savedInstanceState != null) {
+ mPostHistory.restoreInstance(savedInstanceState);
+ }
+ }
+
+ @Override
+ public void setArguments(Bundle args) {
+ super.setArguments(args);
+ if (args != null) {
+ mBlogId = args.getLong(ReaderConstants.ARG_BLOG_ID);
+ mPostId = args.getLong(ReaderConstants.ARG_POST_ID);
+ mIsRelatedPost = args.getBoolean(ReaderConstants.ARG_IS_RELATED_POST);
+ if (args.containsKey(ReaderConstants.ARG_POST_LIST_TYPE)) {
+ mPostListType = (ReaderPostListType) args.getSerializable(ReaderConstants.ARG_POST_LIST_TYPE);
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (activity instanceof AutoHideToolbarListener) {
+ mAutoHideToolbarListener = (AutoHideToolbarListener) activity;
+ }
+ mToolbarHeight = activity.getResources().getDimensionPixelSize(R.dimen.toolbar_height);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.reader_fragment_post_detail, container, false);
+
+ CustomSwipeRefreshLayout swipeRefreshLayout = (CustomSwipeRefreshLayout) view.findViewById(R.id.swipe_to_refresh);
+
+ //this fragment hides/shows toolbar with scrolling, which messes up ptr animation position
+ //so we have to set it manually
+ int swipeToRefreshOffset = getResources().getDimensionPixelSize(R.dimen.toolbar_content_offset);
+ swipeRefreshLayout.setProgressViewOffset(false, 0, swipeToRefreshOffset);
+
+ mSwipeToRefreshHelper = new SwipeToRefreshHelper(getActivity(), swipeRefreshLayout, new SwipeToRefreshHelper.RefreshListener() {
+ @Override
+ public void onRefreshStarted() {
+ if (!isAdded()) {
+ return;
+ }
+
+ updatePost();
+ }
+ });
+
+ mScrollView = (WPScrollView) view.findViewById(R.id.scroll_view_reader);
+ mScrollView.setScrollDirectionListener(this);
+
+ mLayoutFooter = (ViewGroup) view.findViewById(R.id.layout_post_detail_footer);
+ mLikingUsersView = (ReaderLikingUsersView) view.findViewById(R.id.layout_liking_users_view);
+ mLikingUsersDivider = view.findViewById(R.id.layout_liking_users_divider);
+ mLikingUsersLabel = view.findViewById(R.id.text_liking_users_label);
+
+ // setup the ReaderWebView
+ mReaderWebView = (ReaderWebView) view.findViewById(R.id.reader_webview);
+ mReaderWebView.setCustomViewListener(this);
+ mReaderWebView.setUrlClickListener(this);
+ mReaderWebView.setPageFinishedListener(this);
+
+ // hide footer and scrollView until the post is loaded
+ mLayoutFooter.setVisibility(View.INVISIBLE);
+ mScrollView.setVisibility(View.INVISIBLE);
+
+ return view;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mReaderWebView != null) {
+ mReaderWebView.destroy();
+ }
+ }
+
+ private boolean hasPost() {
+ return (mPost != null);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ menu.clear();
+ inflater.inflate(R.menu.reader_detail, menu);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+
+ // browse & share require the post to have a URL (some feed-based posts don't have one)
+ boolean postHasUrl = hasPost() && mPost.hasUrl();
+ MenuItem mnuBrowse = menu.findItem(R.id.menu_browse);
+ if (mnuBrowse != null) {
+ mnuBrowse.setVisible(postHasUrl);
+ }
+ MenuItem mnuShare = menu.findItem(R.id.menu_share);
+ if (mnuShare != null) {
+ mnuShare.setVisible(postHasUrl);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int i = item.getItemId();
+ if (i == R.id.menu_browse) {
+ if (hasPost()) {
+ ReaderActivityLauncher.openUrl(getActivity(), mPost.getUrl(), OpenUrlType.EXTERNAL);
+ }
+ return true;
+ } else if (i == R.id.menu_share) {
+ AnalyticsTracker.track(AnalyticsTracker.Stat.SHARED_ITEM);
+ sharePage();
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private ReaderPostListType getPostListType() {
+ return (mPostListType != null ? mPostListType : ReaderTypes.DEFAULT_POST_LIST_TYPE);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putLong(ReaderConstants.ARG_BLOG_ID, mBlogId);
+ outState.putLong(ReaderConstants.ARG_POST_ID, mPostId);
+
+ outState.putBoolean(ReaderConstants.ARG_IS_RELATED_POST, mIsRelatedPost);
+ outState.putBoolean(ReaderConstants.KEY_ALREADY_UPDATED, mHasAlreadyUpdatedPost);
+ outState.putBoolean(ReaderConstants.KEY_ALREADY_REQUESTED, mHasAlreadyRequestedPost);
+
+ outState.putSerializable(ReaderConstants.ARG_POST_LIST_TYPE, getPostListType());
+
+ mPostHistory.saveInstance(outState);
+
+ if (!TextUtils.isEmpty(mErrorMessage)) {
+ outState.putString(ReaderConstants.KEY_ERROR_MESSAGE, mErrorMessage);
+ }
+
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ setHasOptionsMenu(true);
+ restoreState(savedInstanceState);
+ }
+
+ private void restoreState(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mBlogId = savedInstanceState.getLong(ReaderConstants.ARG_BLOG_ID);
+ mPostId = savedInstanceState.getLong(ReaderConstants.ARG_POST_ID);
+ mIsRelatedPost = savedInstanceState.getBoolean(ReaderConstants.ARG_IS_RELATED_POST);
+ mHasAlreadyUpdatedPost = savedInstanceState.getBoolean(ReaderConstants.KEY_ALREADY_UPDATED);
+ mHasAlreadyRequestedPost = savedInstanceState.getBoolean(ReaderConstants.KEY_ALREADY_REQUESTED);
+ if (savedInstanceState.containsKey(ReaderConstants.ARG_POST_LIST_TYPE)) {
+ mPostListType = (ReaderPostListType) savedInstanceState.getSerializable(ReaderConstants.ARG_POST_LIST_TYPE);
+ }
+ if (savedInstanceState.containsKey(ReaderConstants.KEY_ERROR_MESSAGE)) {
+ mErrorMessage = savedInstanceState.getString(ReaderConstants.KEY_ERROR_MESSAGE);
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ EventBus.getDefault().register(this);
+ if (!hasPost()) {
+ showPost();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ EventBus.getDefault().unregister(this);
+ }
+
+ /*
+ * called by the activity when user hits the back button - returns true if the back button
+ * is handled here and should be ignored by the activity
+ */
+ @Override
+ public boolean onActivityBackPressed() {
+ return goBackInPostHistory();
+ }
+
+ /*
+ * changes the like on the passed post
+ */
+ private void togglePostLike() {
+ if (!isAdded() || !hasPost() || !NetworkUtils.checkConnection(getActivity())) {
+ return;
+ }
+
+ boolean isAskingToLike = !mPost.isLikedByCurrentUser;
+ ReaderIconCountView likeCount = (ReaderIconCountView) getView().findViewById(R.id.count_likes);
+ likeCount.setSelected(isAskingToLike);
+ ReaderAnim.animateLikeButton(likeCount.getImageView(), isAskingToLike);
+
+ boolean success = ReaderPostActions.performLikeAction(mPost, isAskingToLike);
+ if (!success) {
+ likeCount.setSelected(!isAskingToLike);
+ return;
+ }
+
+ // get the post again since it has changed, then refresh to show changes
+ mPost = ReaderPostTable.getPost(mBlogId, mPostId, false);
+ refreshLikes();
+ refreshIconCounts();
+
+ if (isAskingToLike) {
+ AnalyticsUtils.trackWithReaderPostDetails(AnalyticsTracker.Stat.READER_ARTICLE_LIKED, mPost);
+ } else {
+ AnalyticsUtils.trackWithReaderPostDetails(AnalyticsTracker.Stat.READER_ARTICLE_UNLIKED, mPost);
+ }
+ }
+
+ /*
+ * user tapped follow button to follow/unfollow the blog this post is from
+ */
+ private void togglePostFollowed() {
+ if (!isAdded() || !hasPost()) {
+ return;
+ }
+
+ final boolean isAskingToFollow = !ReaderPostTable.isPostFollowed(mPost);
+ final ReaderFollowButton followButton = (ReaderFollowButton) getView().findViewById(R.id.follow_button);
+
+ ReaderActions.ActionListener listener = new ReaderActions.ActionListener() {
+ @Override
+ public void onActionResult(boolean succeeded) {
+ if (!isAdded()) {
+ return;
+ }
+ followButton.setEnabled(true);
+ if (!succeeded) {
+ int resId = (isAskingToFollow ? R.string.reader_toast_err_follow_blog : R.string.reader_toast_err_unfollow_blog);
+ ToastUtils.showToast(getActivity(), resId);
+ followButton.setIsFollowedAnimated(!isAskingToFollow);
+ }
+ }
+ };
+
+ followButton.setEnabled(false);
+
+ if (ReaderBlogActions.followBlogForPost(mPost, isAskingToFollow, listener)) {
+ followButton.setIsFollowedAnimated(isAskingToFollow);
+ }
+ }
+
+ /*
+ * display the standard Android share chooser to share this post
+ */
+ private static final int MAX_SHARE_TITLE_LEN = 100;
+
+ private void sharePage() {
+ if (!isAdded() || !hasPost()) {
+ return;
+ }
+
+ final String url = (mPost.hasShortUrl() ? mPost.getShortUrl() : mPost.getUrl());
+ final String shareText;
+
+ if (mPost.hasTitle()) {
+ final String title;
+ // we don't know where the user will choose to share, so enforce a max title length
+ // in order to fit a tweet with some extra room for the URL and user edits
+ if (mPost.getTitle().length() > MAX_SHARE_TITLE_LEN) {
+ title = mPost.getTitle().substring(0, MAX_SHARE_TITLE_LEN).trim() + "…";
+ } else {
+ title = mPost.getTitle().trim();
+ }
+ shareText = title + " - " + url;
+ } else {
+ shareText = url;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_TEXT, shareText);
+ intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.reader_share_subject, getString(R.string.app_name)));
+ try {
+ startActivity(Intent.createChooser(intent, getString(R.string.reader_share_link)));
+ } catch (android.content.ActivityNotFoundException ex) {
+ ToastUtils.showToast(getActivity(), R.string.reader_toast_err_share_intent);
+ }
+ }
+
+ /*
+ * replace the current post with the passed one
+ */
+ private void replacePost(long blogId, long postId) {
+ mBlogId = blogId;
+ mPostId = postId;
+ mHasAlreadyRequestedPost = false;
+ mHasAlreadyUpdatedPost = false;
+
+ // hide views that would show info for the previous post - these will be re-displayed
+ // with the correct info once the new post loads
+ getView().findViewById(R.id.container_related_posts).setVisibility(View.GONE);
+ getView().findViewById(R.id.text_related_posts_label).setVisibility(View.GONE);
+
+ mLikingUsersView.setVisibility(View.GONE);
+ mLikingUsersDivider.setVisibility(View.GONE);
+ mLikingUsersLabel.setVisibility(View.GONE);
+
+ // clear the webView - otherwise it will remain scrolled to where the user scrolled to
+ mReaderWebView.clearContent();
+
+ // make sure the toolbar and footer are showing
+ showToolbar(true);
+ showFooter(true);
+
+ // now show the passed post
+ showPost();
+ }
+
+ /*
+ * request posts related to the current one - only available for wp.com
+ */
+ private void requestRelatedPosts() {
+ if (hasPost() && mPost.isWP()) {
+ ReaderPostActions.requestRelatedPosts(mPost);
+ }
+ }
+
+ /*
+ * related posts were retrieved, so show them
+ */
+ @SuppressWarnings("unused")
+ public void onEventMainThread(ReaderEvents.RelatedPostsUpdated event) {
+ if (!isAdded() || !hasPost()) return;
+
+ // make sure this is for the current post
+ if (event.getSourcePost().postId == mPostId && event.getSourcePost().blogId == mBlogId) {
+ showRelatedPosts(event.getRelatedPosts());
+ }
+ }
+
+ private void showRelatedPosts(@NonNull ReaderRelatedPostList relatedPosts) {
+ // locate the related posts container and remove any existing related post views
+ ViewGroup container = (ViewGroup) getView().findViewById(R.id.container_related_posts);
+ container.removeAllViews();
+
+ // add a separate view for each related post
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ int imageSize = DisplayUtils.dpToPx(getActivity(), getResources().getDimensionPixelSize(R.dimen.reader_related_post_image_size));
+ for (int index = 0; index < relatedPosts.size(); index++) {
+ final ReaderRelatedPost relatedPost = relatedPosts.get(index);
+
+ View postView = inflater.inflate(R.layout.reader_related_post, container, false);
+ TextView txtTitle = (TextView) postView.findViewById(R.id.text_related_post_title);
+ TextView txtByline = (TextView) postView.findViewById(R.id.text_related_post_byline);
+ WPNetworkImageView imgFeatured = (WPNetworkImageView) postView.findViewById(R.id.image_related_post);
+
+ txtTitle.setText(relatedPost.getTitle());
+ txtByline.setText(relatedPost.getByline());
+
+ imgFeatured.setVisibility(relatedPost.hasFeaturedImage() ? View.VISIBLE : View.GONE);
+ if (relatedPost.hasFeaturedImage()) {
+ String imageUrl = PhotonUtils.getPhotonImageUrl(relatedPost.getFeaturedImage(), imageSize, imageSize);
+ imgFeatured.setImageUrl(imageUrl, WPNetworkImageView.ImageType.PHOTO_ROUNDED);
+ imgFeatured.setVisibility(View.VISIBLE);
+ }
+
+ // tapping this view should open the related post detail
+ postView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ showRelatedPostDetail(relatedPost.getBlogId(), relatedPost.getPostId());
+ }
+ });
+
+ container.addView(postView);
+
+ // add a divider below all but the last related post
+ if (index < relatedPosts.size() - 1) {
+ View dividerView = inflater.inflate(R.layout.reader_related_post_divider, container, false);
+ container.addView(dividerView);
+ }
+ }
+
+ View label = getView().findViewById(R.id.text_related_posts_label);
+ if (label.getVisibility() != View.VISIBLE) {
+ AniUtils.fadeIn(label, AniUtils.Duration.MEDIUM);
+ }
+ if (container.getVisibility() != View.VISIBLE) {
+ AniUtils.fadeIn(container, AniUtils.Duration.MEDIUM);
+ }
+ }
+
+ /*
+ * user clicked a single related post - if we're already viewing a related post, add it to the
+ * history stack so the user can back-button through the history - otherwise start a new detail
+ * activity for this related post
+ */
+ private void showRelatedPostDetail(long blogId, long postId) {
+ AnalyticsUtils.trackWithReaderPostDetails(
+ AnalyticsTracker.Stat.READER_RELATED_POST_CLICKED, blogId, postId);
+
+ if (mIsRelatedPost) {
+ mPostHistory.push(new ReaderBlogIdPostId(mPost.blogId, mPost.postId));
+ replacePost(blogId, postId);
+ } else {
+ ReaderActivityLauncher.showReaderPostDetail(getActivity(), blogId, postId, true);
+ }
+ }
+
+ /*
+ * if the fragment is maintaining a backstack of posts, navigate to the previous one
+ */
+ protected boolean goBackInPostHistory() {
+ if (!mPostHistory.isEmpty()) {
+ ReaderBlogIdPostId ids = mPostHistory.pop();
+ replacePost(ids.getBlogId(), ids.getPostId());
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ * get the latest version of this post
+ */
+ private void updatePost() {
+ if (!hasPost() || !mPost.isWP()) {
+ setRefreshing(false);
+ return;
+ }
+
+ final int numLikesBefore = ReaderLikeTable.getNumLikesForPost(mPost);
+
+ ReaderActions.UpdateResultListener resultListener = new ReaderActions.UpdateResultListener() {
+ @Override
+ public void onUpdateResult(ReaderActions.UpdateResult result) {
+ if (!isAdded()) {
+ return;
+ }
+ // if the post has changed, reload it from the db and update the like/comment counts
+ if (result.isNewOrChanged()) {
+ mPost = ReaderPostTable.getPost(mBlogId, mPostId, false);
+ refreshIconCounts();
+ }
+ // refresh likes if necessary - done regardless of whether the post has changed
+ // since it's possible likes weren't stored until the post was updated
+ if (result != ReaderActions.UpdateResult.FAILED
+ && numLikesBefore != ReaderLikeTable.getNumLikesForPost(mPost)) {
+ refreshLikes();
+ }
+
+ setRefreshing(false);
+ }
+ };
+ ReaderPostActions.updatePost(mPost, resultListener);
+ }
+
+ private void refreshIconCounts() {
+ if (!isAdded() || !hasPost() || !canShowFooter()) {
+ return;
+ }
+
+ final ReaderIconCountView countLikes = (ReaderIconCountView) getView().findViewById(R.id.count_likes);
+ final ReaderIconCountView countComments = (ReaderIconCountView) getView().findViewById(R.id.count_comments);
+
+ if (canShowCommentCount()) {
+ countComments.setCount(mPost.numReplies);
+ countComments.setVisibility(View.VISIBLE);
+ countComments.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ReaderActivityLauncher.showReaderComments(getActivity(), mPost.blogId, mPost.postId);
+ }
+ });
+ } else {
+ countComments.setVisibility(View.INVISIBLE);
+ countComments.setOnClickListener(null);
+ }
+
+ if (canShowLikeCount()) {
+ countLikes.setCount(mPost.numLikes);
+ countLikes.setVisibility(View.VISIBLE);
+ countLikes.setSelected(mPost.isLikedByCurrentUser);
+ if (mIsLoggedOutReader) {
+ countLikes.setEnabled(false);
+ } else if (mPost.canLikePost()) {
+ countLikes.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ togglePostLike();
+ }
+ });
+ }
+ // if we know refreshLikes() is going to show the liking users, force liking user
+ // views to take up space right now
+ if (mPost.numLikes > 0 && mLikingUsersView.getVisibility() == View.GONE) {
+ mLikingUsersView.setVisibility(View.INVISIBLE);
+ mLikingUsersDivider.setVisibility(View.INVISIBLE);
+ mLikingUsersLabel.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ countLikes.setVisibility(View.INVISIBLE);
+ countLikes.setOnClickListener(null);
+ }
+ }
+
+ /*
+ * show latest likes for this post
+ */
+ private void refreshLikes() {
+ if (!isAdded() || !hasPost() || !mPost.canLikePost()) {
+ return;
+ }
+
+ // nothing more to do if no likes
+ if (mPost.numLikes == 0) {
+ mLikingUsersView.setVisibility(View.GONE);
+ mLikingUsersDivider.setVisibility(View.GONE);
+ mLikingUsersLabel.setVisibility(View.GONE);
+ return;
+ }
+
+ // clicking likes view shows activity displaying all liking users
+ mLikingUsersView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ ReaderActivityLauncher.showReaderLikingUsers(getActivity(), mPost.blogId, mPost.postId);
+ }
+ });
+
+ mLikingUsersDivider.setVisibility(View.VISIBLE);
+ mLikingUsersLabel.setVisibility(View.VISIBLE);
+ mLikingUsersView.setVisibility(View.VISIBLE);
+ mLikingUsersView.showLikingUsers(mPost);
+ }
+
+ private boolean showPhotoViewer(String imageUrl, View sourceView, int startX, int startY) {
+ if (!isAdded() || TextUtils.isEmpty(imageUrl)) {
+ return false;
+ }
+
+ // make sure this is a valid web image (could be file: or data:)
+ if (!imageUrl.startsWith("http")) {
+ return false;
+ }
+
+ String postContent = (mRenderer != null ? mRenderer.getRenderedHtml() : null);
+ boolean isPrivatePost = (mPost != null && mPost.isPrivate);
+ EnumSet<PhotoViewerOption> options = EnumSet.noneOf(PhotoViewerOption.class);
+ if (isPrivatePost) {
+ options.add(ReaderActivityLauncher.PhotoViewerOption.IS_PRIVATE_IMAGE);
+ }
+
+ ReaderActivityLauncher.showReaderPhotoViewer(
+ getActivity(),
+ imageUrl,
+ postContent,
+ sourceView,
+ options,
+ startX,
+ startY);
+
+ return true;
+ }
+
+ /*
+ * called when the post doesn't exist in local db, need to get it from server
+ */
+ private void requestPost() {
+ final ProgressBar progress = (ProgressBar) getView().findViewById(R.id.progress_loading);
+ progress.setVisibility(View.VISIBLE);
+ progress.bringToFront();
+
+ ReaderActions.OnRequestListener listener = new ReaderActions.OnRequestListener() {
+ @Override
+ public void onSuccess() {
+ if (isAdded()) {
+ progress.setVisibility(View.GONE);
+ showPost();
+ }
+ }
+
+ @Override
+ public void onFailure(int statusCode) {
+ if (isAdded()) {
+ progress.setVisibility(View.GONE);
+ int errMsgResId;
+ if (!NetworkUtils.isNetworkAvailable(getActivity())) {
+ errMsgResId = R.string.no_network_message;
+ } else {
+ switch (statusCode) {
+ case 401:
+ case 403:
+ errMsgResId = R.string.reader_err_get_post_not_authorized;
+ break;
+ case 404:
+ errMsgResId = R.string.reader_err_get_post_not_found;
+ break;
+ default:
+ errMsgResId = R.string.reader_err_get_post_generic;
+ break;
+ }
+ }
+ showError(getString(errMsgResId));
+ }
+ }
+ };
+ ReaderPostActions.requestPost(mBlogId, mPostId, listener);
+ }
+
+ /*
+ * shows an error message in the middle of the screen - used when requesting post fails
+ */
+ private void showError(String errorMessage) {
+ if (!isAdded()) return;
+
+ TextView txtError = (TextView) getView().findViewById(R.id.text_error);
+ txtError.setText(errorMessage);
+ if (txtError.getVisibility() != View.VISIBLE) {
+ AniUtils.fadeIn(txtError, AniUtils.Duration.MEDIUM);
+ }
+ mErrorMessage = errorMessage;
+ }
+
+ private void showPost() {
+ if (mIsPostTaskRunning) {
+ AppLog.w(T.READER, "reader post detail > show post task already running");
+ return;
+ }
+
+ new ShowPostTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ /*
+ * AsyncTask to retrieve this post from SQLite and display it
+ */
+ private boolean mIsPostTaskRunning = false;
+
+ private class ShowPostTask extends AsyncTask<Void, Void, Boolean> {
+ @Override
+ protected void onPreExecute() {
+ mIsPostTaskRunning = true;
+ }
+
+ @Override
+ protected void onCancelled() {
+ mIsPostTaskRunning = false;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ mPost = ReaderPostTable.getPost(mBlogId, mPostId, false);
+ if (mPost == null) {
+ return false;
+ }
+
+ // "discover" Editor Pick posts should open the original (source) post
+ if (mPost.isDiscoverPost()) {
+ ReaderPostDiscoverData discoverData = mPost.getDiscoverData();
+ if (discoverData != null
+ && discoverData.getDiscoverType() == ReaderPostDiscoverData.DiscoverType.EDITOR_PICK
+ && discoverData.getBlogId() != 0
+ && discoverData.getPostId() != 0) {
+ mBlogId = discoverData.getBlogId();
+ mPostId = discoverData.getPostId();
+ mPost = ReaderPostTable.getPost(mBlogId, mPostId, false);
+ if (mPost == null) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ mIsPostTaskRunning = false;
+
+ if (!isAdded()) return;
+
+ // make sure options menu reflects whether we now have a post
+ getActivity().invalidateOptionsMenu();
+
+ if (!result) {
+ // post couldn't be loaded which means it doesn't exist in db, so request it from
+ // the server if it hasn't already been requested
+ if (!mHasAlreadyRequestedPost) {
+ mHasAlreadyRequestedPost = true;
+ AppLog.i(T.READER, "reader post detail > post not found, requesting it");
+ requestPost();
+ } else if (!TextUtils.isEmpty(mErrorMessage)) {
+ // post has already been requested and failed, so restore previous error message
+ showError(mErrorMessage);
+ }
+ return;
+ }
+
+ mReaderWebView.setIsPrivatePost(mPost.isPrivate);
+ mReaderWebView.setBlogSchemeIsHttps(UrlUtils.isHttps(mPost.getBlogUrl()));
+
+ TextView txtTitle = (TextView) getView().findViewById(R.id.text_title);
+ TextView txtBlogName = (TextView) getView().findViewById(R.id.text_blog_name);
+ TextView txtDomain = (TextView) getView().findViewById(R.id.text_domain);
+ TextView txtDateline = (TextView) getView().findViewById(R.id.text_dateline);
+ TextView txtTag = (TextView) getView().findViewById(R.id.text_tag);
+
+ WPNetworkImageView imgBlavatar = (WPNetworkImageView) getView().findViewById(R.id.image_blavatar);
+ WPNetworkImageView imgAvatar = (WPNetworkImageView) getView().findViewById(R.id.image_avatar);
+
+ ViewGroup layoutHeader = (ViewGroup) getView().findViewById(R.id.layout_post_detail_header);
+ ReaderFollowButton followButton = (ReaderFollowButton) layoutHeader.findViewById(R.id.follow_button);
+
+ if (!canShowFooter()) {
+ mLayoutFooter.setVisibility(View.GONE);
+ }
+
+ // add padding to the scrollView to make room for the top and bottom toolbars - this also
+ // ensures the scrollbar matches the content so it doesn't disappear behind the toolbars
+ int topPadding = (mAutoHideToolbarListener != null ? mToolbarHeight : 0);
+ int bottomPadding = (canShowFooter() ? mLayoutFooter.getHeight() : 0);
+ mScrollView.setPadding(0, topPadding, 0, bottomPadding);
+
+ // scrollView was hidden in onCreateView, show it now that we have the post
+ mScrollView.setVisibility(View.VISIBLE);
+
+ // render the post in the webView
+ mRenderer = new ReaderPostRenderer(mReaderWebView, mPost);
+ mRenderer.beginRender();
+
+ txtTitle.setText(mPost.hasTitle() ? mPost.getTitle() : getString(R.string.reader_untitled_post));
+
+ followButton.setVisibility(mIsLoggedOutReader ? View.GONE : View.VISIBLE);
+ if (!mIsLoggedOutReader) {
+ followButton.setIsFollowed(mPost.isFollowedByCurrentUser);
+ followButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ togglePostFollowed();
+ }
+ });
+ }
+
+ // clicking the header shows blog preview
+ if (getPostListType() != ReaderPostListType.BLOG_PREVIEW) {
+ layoutHeader.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ReaderActivityLauncher.showReaderBlogPreview(v.getContext(), mPost);
+ }
+ });
+ }
+
+ if (mPost.hasBlogName()) {
+ txtBlogName.setText(mPost.getBlogName());
+ } else if (mPost.hasAuthorName()) {
+ txtBlogName.setText(mPost.getAuthorName());
+ } else {
+ txtBlogName.setText(null);
+ }
+
+ if (mPost.hasBlogUrl()) {
+ int blavatarSz = getResources().getDimensionPixelSize(R.dimen.avatar_sz_medium);
+ String imageUrl = GravatarUtils.blavatarFromUrl(mPost.getBlogUrl(), blavatarSz);
+ imgBlavatar.setImageUrl(imageUrl, WPNetworkImageView.ImageType.BLAVATAR);
+ txtDomain.setText(UrlUtils.getHost(mPost.getBlogUrl()));
+ } else {
+ imgBlavatar.showDefaultBlavatarImage();
+ txtDomain.setText(null);
+ }
+
+ if (mPost.hasPostAvatar()) {
+ int avatarSz = getResources().getDimensionPixelSize(R.dimen.avatar_sz_tiny);
+ imgAvatar.setImageUrl(mPost.getPostAvatarForDisplay(avatarSz), WPNetworkImageView.ImageType.AVATAR);
+ } else {
+ imgAvatar.showDefaultGravatarImage();
+ }
+
+ String timestamp = DateTimeUtils.javaDateToTimeSpan(mPost.getDisplayDate(), WordPress.getContext());
+ if (mPost.hasAuthorName()) {
+ txtDateline.setText(mPost.getAuthorName() + ReaderConstants.UNICODE_BULLET_WITH_SPACE + timestamp);
+ } else if (mPost.hasBlogName()) {
+ txtDateline.setText(mPost.getBlogName() + ReaderConstants.UNICODE_BULLET_WITH_SPACE + timestamp);
+ } else {
+ txtDateline.setText(timestamp);
+ }
+
+ final String tagToDisplay = mPost.getTagForDisplay(null);
+ if (!TextUtils.isEmpty(tagToDisplay)) {
+ txtTag.setText(ReaderUtils.makeHashTag(tagToDisplay));
+ txtTag.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ReaderTag tag = ReaderUtils.getTagFromTagName(tagToDisplay, ReaderTagType.FOLLOWED);
+ ReaderActivityLauncher.showReaderTagPreview(v.getContext(), tag);
+ }
+ });
+ }
+
+ if (canShowFooter() && mLayoutFooter.getVisibility() != View.VISIBLE) {
+ AniUtils.fadeIn(mLayoutFooter, AniUtils.Duration.LONG);
+ }
+
+ refreshIconCounts();
+ }
+ }
+
+ /*
+ * called by the web view when the content finishes loading - likes aren't displayed
+ * until this is triggered, to avoid having them appear before the webView content
+ */
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ if (!isAdded()) {
+ return;
+ }
+
+ if (url != null && url.equals("about:blank")) {
+ // brief delay before showing comments/likes to give page time to render
+ view.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (!isAdded()) {
+ return;
+ }
+ refreshLikes();
+ if (!mHasAlreadyUpdatedPost) {
+ mHasAlreadyUpdatedPost = true;
+ updatePost();
+ }
+ requestRelatedPosts();
+ }
+ }, 300);
+ } else {
+ AppLog.w(T.READER, "reader post detail > page finished - " + url);
+ }
+ }
+
+ /*
+ * return the container view that should host the full screen video
+ */
+ @Override
+ public ViewGroup onRequestCustomView() {
+ if (isAdded()) {
+ return (ViewGroup) getView().findViewById(R.id.layout_custom_view_container);
+ } else {
+ return null;
+ }
+ }
+
+ /*
+ * return the container view that should be hidden when full screen video is shown
+ */
+ @Override
+ public ViewGroup onRequestContentView() {
+ if (isAdded()) {
+ return (ViewGroup) getView().findViewById(R.id.layout_post_detail_container);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void onCustomViewShown() {
+ // full screen video has just been shown so hide the ActionBar
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.hide();
+ }
+ }
+
+ @Override
+ public void onCustomViewHidden() {
+ // user returned from full screen video so re-display the ActionBar
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.show();
+ }
+ }
+
+ boolean isCustomViewShowing() {
+ return mReaderWebView != null && mReaderWebView.isCustomViewShowing();
+ }
+
+ void hideCustomView() {
+ if (mReaderWebView != null) {
+ mReaderWebView.hideCustomView();
+ }
+ }
+
+ @Override
+ public boolean onUrlClick(String url) {
+ // if this is a "wordpress://blogpreview?" link, show blog preview for the blog - this is
+ // used for Discover posts that highlight a blog
+ if (ReaderUtils.isBlogPreviewUrl(url)) {
+ long blogId = ReaderUtils.getBlogIdFromBlogPreviewUrl(url);
+ if (blogId != 0) {
+ ReaderActivityLauncher.showReaderBlogPreview(getActivity(), blogId);
+ }
+ return true;
+ }
+
+ // open YouTube videos in external app so they launch the YouTube player, open all other
+ // urls using an AuthenticatedWebViewActivity
+ final OpenUrlType openUrlType;
+ if (ReaderVideoUtils.isYouTubeVideoLink(url)) {
+ openUrlType = OpenUrlType.EXTERNAL;
+ } else {
+ openUrlType = OpenUrlType.INTERNAL;
+ }
+ ReaderActivityLauncher.openUrl(getActivity(), url, openUrlType);
+ return true;
+ }
+
+ @Override
+ public boolean onImageUrlClick(String imageUrl, View view, int x, int y) {
+ return showPhotoViewer(imageUrl, view, x, y);
+ }
+
+ private ActionBar getActionBar() {
+ if (isAdded() && getActivity() instanceof AppCompatActivity) {
+ return ((AppCompatActivity) getActivity()).getSupportActionBar();
+ } else {
+ AppLog.w(T.READER, "reader post detail > getActionBar returned null");
+ return null;
+ }
+ }
+
+ void pauseWebView() {
+ if (mReaderWebView == null) {
+ AppLog.w(T.READER, "reader post detail > attempt to pause null webView");
+ } else if (!mIsWebViewPaused) {
+ AppLog.d(T.READER, "reader post detail > pausing webView");
+ mReaderWebView.hideCustomView();
+ mReaderWebView.onPause();
+ mIsWebViewPaused = true;
+ }
+ }
+
+ void resumeWebViewIfPaused() {
+ if (mReaderWebView == null) {
+ AppLog.w(T.READER, "reader post detail > attempt to resume null webView");
+ } else if (mIsWebViewPaused) {
+ AppLog.d(T.READER, "reader post detail > resuming paused webView");
+ mReaderWebView.onResume();
+ mIsWebViewPaused = false;
+ }
+ }
+
+ @Override
+ public void onScrollUp(float distanceY) {
+ if (!mIsToolbarShowing
+ && -distanceY >= MIN_SCROLL_DISTANCE_Y) {
+ showToolbar(true);
+ showFooter(true);
+ }
+ }
+
+ @Override
+ public void onScrollDown(float distanceY) {
+ if (mIsToolbarShowing
+ && distanceY >= MIN_SCROLL_DISTANCE_Y
+ && mScrollView.canScrollDown()
+ && mScrollView.canScrollUp()
+ && mScrollView.getScrollY() > mToolbarHeight) {
+ showToolbar(false);
+ showFooter(false);
+ }
+ }
+
+ @Override
+ public void onScrollCompleted() {
+ if (!mIsToolbarShowing
+ && (!mScrollView.canScrollDown() || !mScrollView.canScrollUp())) {
+ showToolbar(true);
+ showFooter(true);
+ }
+ }
+
+ private void showToolbar(boolean show) {
+ mIsToolbarShowing = show;
+ if (mAutoHideToolbarListener != null) {
+ mAutoHideToolbarListener.onShowHideToolbar(show);
+ }
+ }
+
+ private void showFooter(boolean show) {
+ if (isAdded() && canShowFooter()) {
+ AniUtils.animateBottomBar(mLayoutFooter, show);
+ }
+ }
+
+ /*
+ * can we show the footer bar which contains the like & comment counts?
+ */
+ private boolean canShowFooter() {
+ return canShowLikeCount() || canShowCommentCount();
+ }
+
+ private boolean canShowCommentCount() {
+ if (mPost == null) {
+ return false;
+ }
+ if (mIsLoggedOutReader) {
+ return mPost.numReplies > 0;
+ }
+ return mPost.isWP()
+ && !mPost.isJetpack
+ && !mPost.isDiscoverPost()
+ && (mPost.isCommentsOpen || mPost.numReplies > 0);
+ }
+
+ private boolean canShowLikeCount() {
+ if (mPost == null) {
+ return false;
+ }
+ if (mIsLoggedOutReader) {
+ return mPost.numLikes > 0;
+ }
+ return mPost.canLikePost() || mPost.numLikes > 0;
+ }
+
+ private void setRefreshing(boolean refreshing) {
+ mSwipeToRefreshHelper.setRefreshing(refreshing);
+ }
+
+}