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 676e40588ec5..cf2037457bd9 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 @@ -3,7 +3,6 @@ import android.support.annotation.NonNull; import org.wordpress.android.models.ReaderPost; -import org.wordpress.android.models.ReaderPostList; import org.wordpress.android.models.ReaderTag; import org.wordpress.android.ui.reader.actions.ReaderActions; import org.wordpress.android.ui.reader.models.ReaderRelatedPostList; @@ -119,17 +118,35 @@ public ReaderActions.UpdateResult getResult() { } public static class RelatedPostsUpdated { - private final ReaderPost mSourcePost; - private final ReaderRelatedPostList mRelatedPosts; - public RelatedPostsUpdated(@NonNull ReaderPost sourcePost, @NonNull ReaderPostList relatedPosts) { - mSourcePost = sourcePost; - mRelatedPosts = new ReaderRelatedPostList(relatedPosts); - } - public ReaderPost getSourcePost() { - return mSourcePost; - } - public ReaderRelatedPostList getRelatedPosts() { - return mRelatedPosts; + private final long mSourcePostId; + private final long mSourceSiteId; + private final ReaderRelatedPostList mLocalRelatedPosts; + private final ReaderRelatedPostList mGlobalRelatedPosts; + public RelatedPostsUpdated(@NonNull ReaderPost sourcePost, + @NonNull ReaderRelatedPostList localRelatedPosts, + @NonNull ReaderRelatedPostList globalRelatedPosts) { + mSourcePostId = sourcePost.postId; + mSourceSiteId = sourcePost.blogId; + mLocalRelatedPosts = localRelatedPosts; + mGlobalRelatedPosts = globalRelatedPosts; + } + public long getSourcePostId() { + return mSourcePostId; + } + public long getSourceSiteId() { + return mSourceSiteId; + } + public ReaderRelatedPostList getLocalRelatedPosts() { + return mLocalRelatedPosts; + } + public ReaderRelatedPostList getGlobalRelatedPosts() { + return mGlobalRelatedPosts; + } + public boolean hasLocalRelatedPosts() { + return mLocalRelatedPosts.size() > 0; + } + public boolean hasGlobalRelatedPosts() { + return mGlobalRelatedPosts.size() > 0; } } } 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 ef52bb92e46a..88c6ab4b6991 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 @@ -34,13 +34,13 @@ import org.wordpress.android.ui.reader.actions.ReaderActions; 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.ReaderIconCountView; import org.wordpress.android.ui.reader.views.ReaderLikingUsersView; import org.wordpress.android.ui.reader.views.ReaderPostDetailHeaderView; +import org.wordpress.android.ui.reader.views.ReaderRelatedPostsView; import org.wordpress.android.ui.reader.views.ReaderTagStrip; import org.wordpress.android.ui.reader.views.ReaderWebView; import org.wordpress.android.ui.reader.views.ReaderWebView.ReaderCustomViewListener; @@ -51,14 +51,11 @@ 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.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; @@ -413,8 +410,8 @@ private void replacePost(long blogId, long postId) { // 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); + getView().findViewById(R.id.related_posts_view_local).setVisibility(View.GONE); + getView().findViewById(R.id.related_posts_view_global).setVisibility(View.GONE); mLikingUsersView.setVisibility(View.GONE); mLikingUsersDivider.setVisibility(View.GONE); @@ -441,67 +438,43 @@ private void requestRelatedPosts() { } /* - * related posts were retrieved, so show them + * related posts were retrieved */ @SuppressWarnings("unused") public void onEventMainThread(ReaderEvents.RelatedPostsUpdated event) { if (!isAdded() || !hasPost()) return; - // make sure this is for the current post - if (event.getSourcePost().postId == mPost.postId && event.getSourcePost().blogId == mPost.blogId) { - showRelatedPosts(event.getRelatedPosts()); + // make sure this event is for the current post + if (event.getSourcePostId() == mPost.postId && event.getSourceSiteId() == mPost.blogId) { + if (event.hasLocalRelatedPosts()) { + showRelatedPosts(event.getLocalRelatedPosts(), false); + } + if (event.hasGlobalRelatedPosts()) { + showRelatedPosts(event.getGlobalRelatedPosts(), true); + } } } - 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); + /* + * show the passed list of related posts - can be either global (related posts from + * across wp.com) or local (related posts from the same site as the current post) + */ + private void showRelatedPosts(@NonNull ReaderRelatedPostList relatedPosts, boolean isGlobal) { + // different container views for global/local related posts + int id = isGlobal ? R.id.related_posts_view_global : R.id.related_posts_view_local; + ReaderRelatedPostsView relatedPostsView = (ReaderRelatedPostsView) getView().findViewById(id); + relatedPostsView.showRelatedPosts(relatedPosts, mPost.getBlogName(), isGlobal); + + // tapping a related posts should open the related post detail + relatedPostsView.setOnRelatedPostClickListener(new ReaderRelatedPostsView.OnRelatedPostClickListener() { + @Override + public void onRelatedPostClick(View v, long siteId, long postId) { + showRelatedPostDetail(siteId, postId); } - } + }); - 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); + if (relatedPostsView.getVisibility() != View.VISIBLE) { + AniUtils.fadeIn(relatedPostsView, AniUtils.Duration.MEDIUM); } } 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 f7d66e1d2d82..48c8e6df3545 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 @@ -11,18 +11,20 @@ import com.android.volley.toolbox.StringRequest; import com.wordpress.rest.RestRequest; +import org.json.JSONArray; import org.json.JSONObject; import org.wordpress.android.WordPress; import org.wordpress.android.datasets.ReaderLikeTable; import org.wordpress.android.datasets.ReaderPostTable; import org.wordpress.android.datasets.ReaderUserTable; import org.wordpress.android.models.ReaderPost; -import org.wordpress.android.models.ReaderPostList; import org.wordpress.android.models.ReaderUserIdList; import org.wordpress.android.models.ReaderUserList; import org.wordpress.android.ui.reader.ReaderEvents; import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResult; import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResultListener; +import org.wordpress.android.ui.reader.models.ReaderRelatedPost; +import org.wordpress.android.ui.reader.models.ReaderRelatedPostList; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.JSONUtils; @@ -40,6 +42,8 @@ public class ReaderPostActions { private static final String TRACKING_REFERRER = "https://wordpress.com/"; private static final Random mRandom = new Random(); + private static final int NUM_RELATED_POSTS_TO_REQUEST = 2; + private ReaderPostActions() { throw new AssertionError(); } @@ -340,7 +344,8 @@ public Map getHeaders() throws AuthFailureError { } /* - * request posts related to the passed one + * request posts related to the passed one, endpoint returns a combined list of related posts + * posts from across wp.com and related posts from the same site as the passed post */ public static void requestRelatedPosts(final ReaderPost sourcePost) { if (sourcePost == null) return; @@ -360,20 +365,36 @@ public void onErrorResponse(VolleyError volleyError) { } }; - String path = "/read/site/" + sourcePost.blogId + "/post/" + sourcePost.postId + "/related"; + String path = "/read/site/" + sourcePost.blogId + + "/post/" + sourcePost.postId + + "/related" + + "?size_local=" + NUM_RELATED_POSTS_TO_REQUEST + + "&size_global=" + NUM_RELATED_POSTS_TO_REQUEST + + "&fields=" + ReaderRelatedPost.RELATED_POST_FIELDS; WordPress.getRestClientUtilsV1_2().get(path, null, null, listener, errorListener); } - private static void handleRelatedPostsResponse(final ReaderPost sourcePost, final JSONObject jsonObject) { + private static void handleRelatedPostsResponse(final ReaderPost sourcePost, + final JSONObject jsonObject) { if (jsonObject == null) return; new Thread() { @Override public void run() { - ReaderPostList relatedPosts = ReaderPostList.fromJson(jsonObject); - if (relatedPosts != null && relatedPosts.size() > 0) { - ReaderPostTable.addOrUpdatePosts(null, relatedPosts); - EventBus.getDefault().post(new ReaderEvents.RelatedPostsUpdated(sourcePost, relatedPosts)); + JSONArray jsonPosts = jsonObject.optJSONArray("posts"); + if (jsonPosts != null) { + ReaderRelatedPostList allRelatedPosts = ReaderRelatedPostList.fromJsonPosts(jsonPosts); + // split into posts from the passed site (local) and from across wp.com (global) + ReaderRelatedPostList localRelatedPosts = new ReaderRelatedPostList(); + ReaderRelatedPostList globalRelatedPosts = new ReaderRelatedPostList(); + for (ReaderRelatedPost relatedPost : allRelatedPosts) { + if (relatedPost.getSiteId() == sourcePost.blogId) { + localRelatedPosts.add(relatedPost); + } else { + globalRelatedPosts.add(relatedPost); + } + } + EventBus.getDefault().post(new ReaderEvents.RelatedPostsUpdated(sourcePost, localRelatedPosts, globalRelatedPosts)); } } }.start(); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/models/ReaderRelatedPost.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/models/ReaderRelatedPost.java index e959689087b3..be93047bfe5a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/models/ReaderRelatedPost.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/models/ReaderRelatedPost.java @@ -1,86 +1,123 @@ package org.wordpress.android.ui.reader.models; -import android.support.annotation.NonNull; import android.text.TextUtils; -import org.wordpress.android.models.ReaderPost; -import org.wordpress.android.util.UrlUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.HtmlUtils; +import org.wordpress.android.util.JSONUtils; /** * simplified version of a reader post that contains only the fields necessary for a related post */ public class ReaderRelatedPost { - private final long mPostId; - private final long mBlogId; - private final String mTitle; - private final String mByline; - private final String mFeaturedImage; - - public ReaderRelatedPost(@NonNull ReaderPost post) { - mPostId = post.postId; - mBlogId = post.blogId; - - mTitle = post.getTitle(); - mFeaturedImage = post.getFeaturedImage(); - - /* - * we want to include the blog name in the byline when it's available, and most sites - * will have a name, but in rare cases there isn't one so we show the domain instead - */ - String blogNameOrDomain; - boolean hasBlogNameOrDomain; - if (post.hasBlogName()) { - blogNameOrDomain = post.getBlogName(); - hasBlogNameOrDomain = true; - } else if (post.hasBlogUrl()) { - blogNameOrDomain = UrlUtils.getHost(post.getBlogUrl()); - hasBlogNameOrDomain = true; - } else { - blogNameOrDomain = null; - hasBlogNameOrDomain = false; + private long mPostId; + private long mSiteId; + private boolean mIsFollowing; + + private String mTitle; + private String mAuthorName; + private String mAuthorAvatarUrl; + private String mExcerpt; + private String mSiteName; + private String mFeaturedImageUrl; + + // these are the specific fields we should ask for when requesting related posts from + // the endpoint - note that we want to avoid ever requesting the post content, since + // that makes the call much heavier + public static final String RELATED_POST_FIELDS = "ID,site_ID,title,excerpt,site_name,is_following,author,featured_image,featured_media"; + + public static ReaderRelatedPost fromJson(JSONObject json) { + if (json == null) { + throw new IllegalArgumentException("ReaderRelatedPost requires a non-null JSONObject"); } - /* - * The byline should show the author name and blog name if both are available, but if - * they're the same (which happens frequently) we only need to show the blog name. - * Otherwise, show either the blog name or author name depending on which is available. - */ - if (post.hasAuthorName() && hasBlogNameOrDomain) { - if (post.getAuthorName().equalsIgnoreCase(blogNameOrDomain)) { - mByline = blogNameOrDomain; - } else { - mByline = post.getAuthorName() + ", " + blogNameOrDomain; + ReaderRelatedPost post = new ReaderRelatedPost(); + + // ID and site_ID are required, so make sure we have them + try { + post.mPostId = json.getLong("ID"); + post.mSiteId = json.getLong("site_ID"); + } catch (JSONException e) { + AppLog.e(AppLog.T.READER, e); + return null; + } + + post.mIsFollowing = JSONUtils.getBool(json, "is_following"); + + post.mTitle = JSONUtils.getStringDecoded(json, "title"); + post.mExcerpt = HtmlUtils.fastStripHtml(JSONUtils.getString(json, "excerpt")).trim(); + post.mSiteName = JSONUtils.getStringDecoded(json, "site_name"); + post.mFeaturedImageUrl = JSONUtils.getString(json, "featured_image"); + + JSONObject jsonAuthor = json.optJSONObject("author"); + if (jsonAuthor != null) { + post.mAuthorName = JSONUtils.getStringDecoded(jsonAuthor, "name"); + post.mAuthorAvatarUrl = JSONUtils.getString(jsonAuthor, "avatar_URL"); + } + + // if there's no featured image, check if featured media has been set to an image + if (!post.hasFeaturedImageUrl() && json.has("featured_media")) { + JSONObject jsonMedia = json.optJSONObject("featured_media"); + String type = JSONUtils.getString(jsonMedia, "type"); + if (type.equals("image")) { + post.mFeaturedImageUrl = JSONUtils.getString(jsonMedia, "uri"); } - } else if (post.hasAuthorName()) { - mByline = post.getAuthorName(); - } else if (hasBlogNameOrDomain) { - mByline = blogNameOrDomain; - } else { - mByline = ""; } + + return post; } public long getPostId() { return mPostId; } - public long getBlogId() { - return mBlogId; + public long getSiteId() { + return mSiteId; } public String getTitle() { return mTitle; } - public String getByline() { - return mByline; + public String getExcerpt() { + return mExcerpt; + } + + public String getSiteName() { + return mSiteName; + } + + public String getAuthorName() { + return mAuthorName; + } + + public String getAuthorAvatarUrl() { + return mAuthorAvatarUrl; + } + + public String getFeaturedImageUrl() { + return mFeaturedImageUrl; + } + + public boolean isFollowing() { + return mIsFollowing; + } + + public void setIsFollowing(boolean isFollowing) { + mIsFollowing = isFollowing; + } + + public boolean hasExcerpt() { + return !TextUtils.isEmpty(mExcerpt); } - public String getFeaturedImage() { - return mFeaturedImage; + public boolean hasAuthorAvatarUrl() { + return !TextUtils.isEmpty(mAuthorAvatarUrl); } - public boolean hasFeaturedImage() { - return !TextUtils.isEmpty(mFeaturedImage); + public boolean hasFeaturedImageUrl() { + return !TextUtils.isEmpty(mFeaturedImageUrl); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/models/ReaderRelatedPostList.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/models/ReaderRelatedPostList.java index 94c1d5b5aace..91476d64f540 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/models/ReaderRelatedPostList.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/models/ReaderRelatedPostList.java @@ -2,17 +2,25 @@ import android.support.annotation.NonNull; -import org.wordpress.android.models.ReaderPost; -import org.wordpress.android.models.ReaderPostList; +import org.json.JSONArray; +import org.json.JSONObject; import java.util.ArrayList; public class ReaderRelatedPostList extends ArrayList { - public ReaderRelatedPostList(@NonNull ReaderPostList posts) { - for (ReaderPost post: posts) { - add(new ReaderRelatedPost(post)); + public static ReaderRelatedPostList fromJsonPosts(@NonNull JSONArray jsonPosts) { + ReaderRelatedPostList posts = new ReaderRelatedPostList(); + for (int i = 0; i < jsonPosts.length(); i++) { + JSONObject jsonRelatedPost = jsonPosts.optJSONObject(i); + if (jsonRelatedPost != null) { + ReaderRelatedPost relatedPost = ReaderRelatedPost.fromJson(jsonRelatedPost); + if (relatedPost != null) { + posts.add(relatedPost); + } + } } + return posts; } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderFollowButton.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderFollowButton.java index efd5484b82c6..9131fb5ed3c5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderFollowButton.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderFollowButton.java @@ -6,6 +6,7 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; @@ -22,31 +23,52 @@ public class ReaderFollowButton extends LinearLayout { private TextView mTextFollow; private ImageView mImageFollow; private boolean mIsFollowed; + private boolean mShowCaption; public ReaderFollowButton(Context context){ super(context); - initView(context); + initView(context, null); } public ReaderFollowButton(Context context, AttributeSet attrs) { super(context, attrs); - initView(context); + initView(context, attrs); } public ReaderFollowButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - initView(context); + initView(context, attrs); } - private void initView(Context context) { + private void initView(Context context, AttributeSet attrs) { inflate(context, R.layout.reader_follow_button, this); mTextFollow = (TextView) findViewById(R.id.text_follow_button); mImageFollow = (ImageView) findViewById(R.id.image_follow_button); + + // default to showing caption, then read the value from passed attributes + mShowCaption = true; + if (attrs != null) { + TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ReaderFollowButton, 0, 0); + if (array != null) { + mShowCaption = array.getBoolean(R.styleable.ReaderFollowButton_wpShowFollowButtonCaption, true); + } + } + + // hide follow text and enlarge the follow icon if there's no caption + if (!mShowCaption) { + mTextFollow.setText(null); + mTextFollow.setVisibility(View.GONE); + int iconSz = context.getResources().getDimensionPixelSize(R.dimen.reader_follow_icon_no_caption); + mImageFollow.getLayoutParams().width = iconSz; + mImageFollow.getLayoutParams().height = iconSz; + } } private void updateFollowText() { + if (mShowCaption) { + mTextFollow.setText(mIsFollowed ? R.string.reader_btn_unfollow : R.string.reader_btn_follow); + } mTextFollow.setSelected(mIsFollowed); - mTextFollow.setText(mIsFollowed ? R.string.reader_btn_unfollow : R.string.reader_btn_follow); mImageFollow.setImageResource(mIsFollowed ? R.drawable.reader_following : R.drawable.reader_follow); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderRelatedPostsView.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderRelatedPostsView.java new file mode 100644 index 000000000000..8fea7d13c179 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderRelatedPostsView.java @@ -0,0 +1,216 @@ +package org.wordpress.android.ui.reader.views; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.wordpress.android.R; +import org.wordpress.android.ui.reader.actions.ReaderActions; +import org.wordpress.android.ui.reader.actions.ReaderBlogActions; +import org.wordpress.android.ui.reader.models.ReaderRelatedPost; +import org.wordpress.android.ui.reader.models.ReaderRelatedPostList; +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.widgets.WPNetworkImageView; + +/** + * used by the detail view to display related posts, which can be either local (related posts + * from the same site as the source post) or global (related posts from across wp.com) + */ +public class ReaderRelatedPostsView extends LinearLayout { + + public interface OnRelatedPostClickListener { + void onRelatedPostClick(View v, long siteId, long postId); + } + + private OnRelatedPostClickListener mClickListener; + private int mFeaturedImageWidth; + private ReaderRelatedPostList mRelatedPostList; + + public ReaderRelatedPostsView(Context context) { + super(context); + initView(context); + } + + public ReaderRelatedPostsView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(context); + } + + public ReaderRelatedPostsView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(context); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public ReaderRelatedPostsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initView(context); + } + + private void initView(Context context) { + inflate(context, R.layout.reader_related_posts_view, this); + mFeaturedImageWidth = DisplayUtils.dpToPx(context, context.getResources().getDimensionPixelSize(R.dimen.reader_related_post_image_width)); + } + + public void setOnRelatedPostClickListener(OnRelatedPostClickListener listener) { + mClickListener = listener; + } + + public void showRelatedPosts(@NonNull ReaderRelatedPostList posts, String siteName, boolean isGlobal) { + mRelatedPostList = posts; + + ViewGroup container = (ViewGroup) findViewById(R.id.container_related_posts); + container.removeAllViews(); + + // nothing more to do if passed list is empty + if (mRelatedPostList.size() == 0) return; + + int avatarSize = DisplayUtils.dpToPx(getContext(), getResources().getDimensionPixelSize(R.dimen.avatar_sz_extra_small)); + + // add a separate view for each related post + LayoutInflater inflater = LayoutInflater.from(getContext()); + for (int index = 0; index < mRelatedPostList.size(); index++) { + final int position = index; + ReaderRelatedPost relatedPost = mRelatedPostList.get(position); + + View postView = inflater.inflate(R.layout.reader_related_post, container, false); + TextView txtTitle = (TextView) postView.findViewById(R.id.text_related_post_title); + TextView txtExcerpt = (TextView) postView.findViewById(R.id.text_related_post_excerpt); + View siteHeader = postView.findViewById(R.id.layout_related_post_site_header); + + txtTitle.setText(relatedPost.getTitle()); + + if (relatedPost.hasExcerpt()) { + txtExcerpt.setText(relatedPost.getExcerpt()); + txtExcerpt.setVisibility(View.VISIBLE); + } else { + txtExcerpt.setVisibility(View.GONE); + } + + // site header only appears for global related posts + if (isGlobal) { + WPNetworkImageView imgAvatar = (WPNetworkImageView) siteHeader.findViewById(R.id.image_avatar); + TextView txtSiteName = (TextView) siteHeader.findViewById(R.id.text_site_name); + TextView txtAuthorName = (TextView) siteHeader.findViewById(R.id.text_author_name); + txtSiteName.setText(relatedPost.getSiteName()); + txtAuthorName.setText(relatedPost.getAuthorName()); + if (relatedPost.hasAuthorAvatarUrl()) { + String avatarUrl = GravatarUtils.fixGravatarUrl(relatedPost.getAuthorAvatarUrl(), avatarSize); + imgAvatar.setImageUrl(avatarUrl, WPNetworkImageView.ImageType.AVATAR); + } else { + imgAvatar.showDefaultGravatarImage(); + } + + final ReaderFollowButton btnFollow = (ReaderFollowButton) siteHeader.findViewById(R.id.related_post_follow_button); + btnFollow.setIsFollowed(relatedPost.isFollowing()); + btnFollow.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + toggleFollowStatus(btnFollow, position); + } + }); + + siteHeader.setVisibility(View.VISIBLE); + } else { + siteHeader.setVisibility(View.GONE); + } + + showFeaturedImage(postView, relatedPost); + + postView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mClickListener != null) { + mClickListener.onRelatedPostClick(view, + mRelatedPostList.get(position).getSiteId(), + mRelatedPostList.get(position).getPostId()); + } + } + }); + + container.addView(postView); + } + + // make sure the label for these related posts has the correct caption + TextView label = (TextView) findViewById(R.id.text_related_posts_label); + if (isGlobal) { + label.setText(getContext().getString(R.string.reader_label_global_related_posts)); + } else { + label.setText(String.format(getContext().getString(R.string.reader_label_local_related_posts), siteName)); + } + } + + /** + * user tapped follow button on a global related post + * @param btnFollow Follow button for the site to follow + * @param position index of the related post in mRelatedPosts + */ + private void toggleFollowStatus(final ReaderFollowButton btnFollow, final int position) { + if (!NetworkUtils.checkConnection(getContext())) return; + + final boolean isAskingToFollow = !mRelatedPostList.get(position).isFollowing(); + + ReaderActions.ActionListener listener = new ReaderActions.ActionListener() { + @Override + public void onActionResult(boolean succeeded) { + if (getContext() == null) return; + + btnFollow.setEnabled(true); + if (succeeded) { + mRelatedPostList.get(position).setIsFollowing(isAskingToFollow); + } else { + int errResId = isAskingToFollow ? R.string.reader_toast_err_follow_blog : R.string.reader_toast_err_unfollow_blog; + ToastUtils.showToast(getContext(), errResId); + btnFollow.setIsFollowed(!isAskingToFollow); + } + } + }; + + // disable follow button until call completes + btnFollow.setEnabled(false); + + ReaderBlogActions.followBlogById(mRelatedPostList.get(position).getSiteId(), isAskingToFollow, listener); + btnFollow.setIsFollowedAnimated(isAskingToFollow); + } + + /** + * shows the featured image for the passed related post, if available + * @param postView parent view which contains the featured image and other related post views + * @param relatedPost related post to operate on + */ + private void showFeaturedImage(final View postView, final ReaderRelatedPost relatedPost) { + final WPNetworkImageView imgFeatured = (WPNetworkImageView) postView.findViewById(R.id.image_featured); + + // post must have an excerpt in order to show featured image (not enough space otherwise) + if (!relatedPost.hasFeaturedImageUrl() || !relatedPost.hasExcerpt()) { + imgFeatured.setVisibility(View.GONE); + return; + } + + // featured image has height set to MATCH_PARENT so wait for parent view's layout to complete + // before loading image so we can set the image height correctly + postView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + postView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + String photonUrl = PhotonUtils.getPhotonImageUrl(relatedPost.getFeaturedImageUrl(), mFeaturedImageWidth, postView.getHeight()); + imgFeatured.setImageUrl(photonUrl, WPNetworkImageView.ImageType.PHOTO); + } + }); + + imgFeatured.setVisibility(View.VISIBLE); + } + +} diff --git a/WordPress/src/main/res/drawable/reader_related_posts_background.xml b/WordPress/src/main/res/drawable/reader_related_posts_background.xml deleted file mode 100644 index f4a6cbd94434..000000000000 --- a/WordPress/src/main/res/drawable/reader_related_posts_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/WordPress/src/main/res/layout/reader_fragment_post_detail.xml b/WordPress/src/main/res/layout/reader_fragment_post_detail.xml index e421e7e04c12..a3abeb5adcb5 100644 --- a/WordPress/src/main/res/layout/reader_fragment_post_detail.xml +++ b/WordPress/src/main/res/layout/reader_fragment_post_detail.xml @@ -46,34 +46,31 @@ android:layout_marginLeft="@dimen/reader_detail_margin" android:layout_marginRight="@dimen/reader_detail_margin" /> - - + android:layout_below="@+id/layout_post_detail_content" + android:orientation="vertical"> + + + + + + diff --git a/WordPress/src/main/res/layout/reader_related_post.xml b/WordPress/src/main/res/layout/reader_related_post.xml index 6f64e009f5eb..f7b10c662d68 100644 --- a/WordPress/src/main/res/layout/reader_related_post.xml +++ b/WordPress/src/main/res/layout/reader_related_post.xml @@ -1,51 +1,109 @@ + + + + android:layout_marginBottom="@dimen/margin_extra_large" + android:background="?android:selectableItemBackground"> + + - + android:layout_marginBottom="@dimen/margin_medium" + android:layout_toRightOf="@+id/image_featured" + android:visibility="gone" + tools:visibility="visible"> - - - + + + + - + android:layout_centerVertical="true" + android:layout_toLeftOf="@+id/related_post_follow_button" + android:layout_toRightOf="@+id/image_avatar" + android:orientation="vertical"> - + + + + + + + + + + \ No newline at end of file diff --git a/WordPress/src/main/res/layout/reader_related_post_divider.xml b/WordPress/src/main/res/layout/reader_related_post_divider.xml deleted file mode 100644 index 488260748d8a..000000000000 --- a/WordPress/src/main/res/layout/reader_related_post_divider.xml +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/WordPress/src/main/res/layout/reader_related_posts_view.xml b/WordPress/src/main/res/layout/reader_related_posts_view.xml new file mode 100644 index 000000000000..4bebb9a42474 --- /dev/null +++ b/WordPress/src/main/res/layout/reader_related_posts_view.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/WordPress/src/main/res/values/attrs.xml b/WordPress/src/main/res/values/attrs.xml index 6aa3afb9f5be..144082aa5bf3 100644 --- a/WordPress/src/main/res/values/attrs.xml +++ b/WordPress/src/main/res/values/attrs.xml @@ -64,6 +64,13 @@ + + + + + diff --git a/WordPress/src/main/res/values/dimens.xml b/WordPress/src/main/res/values/dimens.xml index db202b68d6fd..30ff799bf7a7 100644 --- a/WordPress/src/main/res/values/dimens.xml +++ b/WordPress/src/main/res/values/dimens.xml @@ -87,9 +87,6 @@ 56dp 64dp - @dimen/margin_extra_large - 48dp - 48dp @@ -115,8 +112,9 @@ 40dp 24dp - 16dp 48dp + 16dp + 24dp 48dp @@ -125,6 +123,8 @@ 340dp @dimen/reader_featured_image_height_default + 72dp + 24dp diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 62a29167f4a0..83077e1ab9b1 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -1145,7 +1145,8 @@ Load more posts Search all public WordPress.com blogs Searching… - Related Reading + More in %s + More on WordPress.com View Gallery 1 image %d images