| 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); |
| } |
| |
| } |