package org.wordpress.android.ui.reader;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.v13.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.analytics.AnalyticsTracker;
import org.wordpress.android.datasets.ReaderPostTable;
import org.wordpress.android.fluxc.store.SiteStore;
import org.wordpress.android.models.ReaderPost;
import org.wordpress.android.models.ReaderTag;
import org.wordpress.android.ui.ActivityLauncher;
import org.wordpress.android.ui.RequestCodes;
import org.wordpress.android.ui.WPLaunchActivity;
import org.wordpress.android.ui.prefs.AppPrefs;
import org.wordpress.android.ui.reader.ReaderTypes.ReaderPostListType;
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.ReaderBlogIdPostIdList;
import org.wordpress.android.ui.reader.services.ReaderPostService;
import org.wordpress.android.util.AnalyticsUtils;
import org.wordpress.android.util.AniUtils;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.NetworkUtils;
import org.wordpress.android.util.ToastUtils;
import org.wordpress.android.widgets.WPSwipeSnackbar;
import org.wordpress.android.widgets.WPViewPager;
import org.wordpress.android.widgets.WPViewPagerTransformer;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import de.greenrobot.event.EventBus;
/*
* shows reader post detail fragments in a ViewPager - primarily used for easy swiping between
* posts with a specific tag or in a specific blog, but can also be used to show a single
* post detail.
*
* It also displays intercepted WordPress.com URls in the following forms
*
* http[s]://wordpress.com/read/blogs/{blogId}/posts/{postId}
* http[s]://wordpress.com/read/feeds/{feedId}/posts/{feedItemId}
* http[s]://{username}.wordpress.com/{year}/{month}/{day}/{postSlug}
*
* Will also handle jumping to the comments section, liking a commend and liking a post directly
*/
public class ReaderPostPagerActivity extends AppCompatActivity
implements ReaderInterfaces.AutoHideToolbarListener {
/**
* Type of URL intercepted
*/
private enum InterceptType {
READER_BLOG,
READER_FEED,
WPCOM_POST_SLUG
}
/**
* operation to perform automatically when opened via deeplinking
*/
public enum DirectOperation {
COMMENT_JUMP,
COMMENT_REPLY,
COMMENT_LIKE,
POST_LIKE,
}
private WPViewPager mViewPager;
private ProgressBar mProgress;
private Toolbar mToolbar;
private ReaderTag mCurrentTag;
private boolean mIsFeed;
private long mBlogId;
private long mPostId;
private int mCommentId;
private DirectOperation mDirectOperation;
private String mInterceptedUri;
private int mLastSelectedPosition = -1;
private ReaderPostListType mPostListType;
private boolean mPostSlugsResolutionUnderway;
private boolean mIsRequestingMorePosts;
private boolean mIsSinglePostView;
private boolean mIsRelatedPostView;
private boolean mBackFromLogin;
private final HashSet<Integer> mTrackedPositions = new HashSet<>();
@Inject SiteStore mSiteStore;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((WordPress) getApplication()).component().inject(this);
setContentView(R.layout.reader_activity_post_pager);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
mViewPager = (WPViewPager) findViewById(R.id.viewpager);
mProgress = (ProgressBar) findViewById(R.id.progress_loading);
if (savedInstanceState != null) {
mIsFeed = savedInstanceState.getBoolean(ReaderConstants.ARG_IS_FEED);
mBlogId = savedInstanceState.getLong(ReaderConstants.ARG_BLOG_ID);
mPostId = savedInstanceState.getLong(ReaderConstants.ARG_POST_ID);
mDirectOperation = (DirectOperation) savedInstanceState
.getSerializable(ReaderConstants.ARG_DIRECT_OPERATION);
mCommentId = savedInstanceState.getInt(ReaderConstants.ARG_COMMENT_ID);
mIsSinglePostView = savedInstanceState.getBoolean(ReaderConstants.ARG_IS_SINGLE_POST);
mIsRelatedPostView = savedInstanceState.getBoolean(ReaderConstants.ARG_IS_RELATED_POST);
mInterceptedUri = savedInstanceState.getString(ReaderConstants.ARG_INTERCEPTED_URI);
if (savedInstanceState.containsKey(ReaderConstants.ARG_POST_LIST_TYPE)) {
mPostListType = (ReaderPostListType) savedInstanceState.getSerializable(ReaderConstants.ARG_POST_LIST_TYPE);
}
if (savedInstanceState.containsKey(ReaderConstants.ARG_TAG)) {
mCurrentTag = (ReaderTag) savedInstanceState.getSerializable(ReaderConstants.ARG_TAG);
}
mPostSlugsResolutionUnderway = savedInstanceState.getBoolean(ReaderConstants.KEY_POST_SLUGS_RESOLUTION_UNDERWAY);
if (savedInstanceState.containsKey(ReaderConstants.KEY_TRACKED_POSITIONS)) {
Serializable positions = savedInstanceState.getSerializable(ReaderConstants.KEY_TRACKED_POSITIONS);
if (positions instanceof HashSet) {
mTrackedPositions.addAll((HashSet<Integer>) positions);
}
}
} else {
mIsFeed = getIntent().getBooleanExtra(ReaderConstants.ARG_IS_FEED, false);
mBlogId = getIntent().getLongExtra(ReaderConstants.ARG_BLOG_ID, 0);
mPostId = getIntent().getLongExtra(ReaderConstants.ARG_POST_ID, 0);
mDirectOperation = (DirectOperation) getIntent()
.getSerializableExtra(ReaderConstants.ARG_DIRECT_OPERATION);
mCommentId = getIntent().getIntExtra(ReaderConstants.ARG_COMMENT_ID, 0);
mIsSinglePostView = getIntent().getBooleanExtra(ReaderConstants.ARG_IS_SINGLE_POST, false);
mIsRelatedPostView = getIntent().getBooleanExtra(ReaderConstants.ARG_IS_RELATED_POST, false);
mInterceptedUri = getIntent().getStringExtra(ReaderConstants.ARG_INTERCEPTED_URI);
if (getIntent().hasExtra(ReaderConstants.ARG_POST_LIST_TYPE)) {
mPostListType = (ReaderPostListType) getIntent().getSerializableExtra(ReaderConstants.ARG_POST_LIST_TYPE);
}
if (getIntent().hasExtra(ReaderConstants.ARG_TAG)) {
mCurrentTag = (ReaderTag) getIntent().getSerializableExtra(ReaderConstants.ARG_TAG);
}
}
if (mPostListType == null) {
mPostListType = ReaderPostListType.TAG_FOLLOWED;
}
// for related posts, show an X in the toolbar which closes the activity - using the
// back button will navigate through related posts
if (mIsRelatedPostView) {
mToolbar.setNavigationIcon(R.drawable.ic_cross_white_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
onShowHideToolbar(true);
trackPostAtPositionIfNeeded(position);
if (mLastSelectedPosition > -1 && mLastSelectedPosition != position) {
// pause the previous web view - important because otherwise embedded content
// will continue to play
ReaderPostDetailFragment lastFragment = getDetailFragmentAtPosition(mLastSelectedPosition);
if (lastFragment != null) {
lastFragment.pauseWebView();
}
// don't show the swipe indicator in the future since the user knows how to swipe
AppPrefs.setReaderSwipeToNavigateShown(true);
}
// resume the newly active webView if it was previously paused
ReaderPostDetailFragment thisFragment = getDetailFragmentAtPosition(position);
if (thisFragment != null) {
thisFragment.resumeWebViewIfPaused();
}
mLastSelectedPosition = position;
updateTitle(position);
}
});
mViewPager.setPageTransformer(false,
new WPViewPagerTransformer(WPViewPagerTransformer.TransformType.SLIDE_OVER));
}
/*
* set the activity title based on the post at the passed position
*/
private void updateTitle(int position) {
// for related posts, always show "Related Post" as the title
if (mIsRelatedPostView) {
setTitle(R.string.reader_title_related_post_detail);
return;
}
// otherwise set the title to the title of the post
ReaderBlogIdPostId ids = getAdapterBlogIdPostIdAtPosition(position);
if (ids != null) {
String title = ReaderPostTable.getPostTitle(ids.getBlogId(), ids.getPostId());
if (!title.isEmpty()) {
setTitle(title);
return;
}
}
// default when post hasn't been retrieved yet
setTitle(isDeepLinking() ? R.string.reader_title_post_detail_wpcom : R.string.reader_title_post_detail);
}
/*
* used by the detail fragment when a post was requested due to not existing locally
*/
@SuppressWarnings("unused")
public void onEventMainThread(ReaderEvents.SinglePostDownloaded event) {
if (!isFinishing()) {
updateTitle(mViewPager.getCurrentItem());
}
}
private boolean isDeepLinking() {
return Intent.ACTION_VIEW.equals(getIntent().getAction());
}
private void handleDeepLinking() {
String action = getIntent().getAction();
Uri uri = getIntent().getData();
AnalyticsUtils.trackWithDeepLinkData(AnalyticsTracker.Stat.DEEP_LINKED, action, uri);
if (uri == null) {
// invalid uri so, just show the entry screen
Intent intent = new Intent(this, WPLaunchActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
return;
}
InterceptType interceptType = InterceptType.READER_BLOG;
String blogIdentifier = null; // can be an id or a slug
String postIdentifier = null; // can be an id or a slug
mInterceptedUri = uri.toString();
List<String> segments = uri.getPathSegments();
// Handled URLs look like this: http[s]://wordpress.com/read/feeds/{feedId}/posts/{feedItemId}
// with the first segment being 'read'.
if (segments != null) {
if (segments.get(0).equals("read")) {
if (segments.size() > 2) {
blogIdentifier = segments.get(2);
if (segments.get(1).equals("blogs")) {
interceptType = InterceptType.READER_BLOG;
} else if (segments.get(1).equals("feeds")) {
interceptType = InterceptType.READER_FEED;
mIsFeed = true;
}
}
if (segments.size() > 4 && segments.get(3).equals("posts")) {
postIdentifier = segments.get(4);
}
parseFragment(uri);
showPost(interceptType, blogIdentifier, postIdentifier);
return;
} else if (segments.size() == 4) {
blogIdentifier = uri.getHost();
try {
postIdentifier = URLEncoder.encode(segments.get(3), "UTF-8");
} catch (UnsupportedEncodingException e) {
AppLog.e(AppLog.T.READER, e);
ToastUtils.showToast(this, R.string.error_generic);
}
parseFragment(uri);
detectLike(uri);
interceptType = InterceptType.WPCOM_POST_SLUG;
showPost(interceptType, blogIdentifier, postIdentifier);
return;
}
}
// at this point, just show the entry screen
Intent intent = new Intent(this, WPLaunchActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
private void showPost(@NonNull InterceptType interceptType, final String blogIdentifier, final String
postIdentifier) {
if (!TextUtils.isEmpty(blogIdentifier) && !TextUtils.isEmpty(postIdentifier)) {
mIsSinglePostView = true;
mIsRelatedPostView = false;
switch (interceptType) {
case READER_BLOG:
if (parseIds(blogIdentifier, postIdentifier)) {
AnalyticsUtils.trackWithBlogPostDetails(AnalyticsTracker.Stat.READER_BLOG_POST_INTERCEPTED,
mBlogId, mPostId);
// IDs have now been set so, let ReaderPostPagerActivity normally display the post
} else {
ToastUtils.showToast(this, R.string.error_generic);
}
break;
case READER_FEED:
if (parseIds(blogIdentifier, postIdentifier)) {
AnalyticsUtils.trackWithFeedPostDetails(AnalyticsTracker.Stat.READER_FEED_POST_INTERCEPTED,
mBlogId, mPostId);
// IDs have now been set so, let ReaderPostPagerActivity normally display the post
} else {
ToastUtils.showToast(this, R.string.error_generic);
}
break;
case WPCOM_POST_SLUG:
AnalyticsUtils.trackWithBlogPostDetails(
AnalyticsTracker.Stat.READER_WPCOM_BLOG_POST_INTERCEPTED, blogIdentifier,
postIdentifier, mCommentId);
// try to get the post from the local db
ReaderPost post = ReaderPostTable.getBlogPost(blogIdentifier, postIdentifier, true);
if (post != null) {
// set the IDs and let ReaderPostPagerActivity normally display the post
mBlogId = post.blogId;
mPostId = post.postId;
} else {
// not stored locally, so request it
ReaderPostActions.requestBlogPost(blogIdentifier, postIdentifier,
new ReaderActions.OnRequestListener() {
@Override
public void onSuccess() {
mPostSlugsResolutionUnderway = false;
ReaderPost post = ReaderPostTable.getBlogPost(blogIdentifier, postIdentifier,
true);
ReaderEvents.PostSlugsRequestCompleted slugsResolved =
(post != null) ? new ReaderEvents.PostSlugsRequestCompleted(
200, post.blogId, post.postId)
: new ReaderEvents.PostSlugsRequestCompleted(200, 0, 0);
// notify that the slug resolution request has completed
EventBus.getDefault().post(slugsResolved);
// post wasn't available locally earlier so, track it now
if (post != null) {
trackPost(post.blogId, post.postId);
}
}
@Override
public void onFailure(int statusCode) {
mPostSlugsResolutionUnderway = false;
// notify that the slug resolution request has completed
EventBus.getDefault().post(new ReaderEvents.PostSlugsRequestCompleted
(statusCode, 0, 0));
}
});
mPostSlugsResolutionUnderway = true;
}
break;
}
} else {
ToastUtils.showToast(this, R.string.error_generic);
}
}
private boolean parseIds(String blogIdentifier, String postIdentifier) {
try {
mBlogId = Long.parseLong(blogIdentifier);
mPostId = Long.parseLong(postIdentifier);
} catch (NumberFormatException e) {
AppLog.e(AppLog.T.READER, e);
return false;
}
return true;
}
/**
* Parse the URL fragment and interpret it as an operation to perform. For example, a "#comments" fragment is
* interpreted as a direct jump into the comments section of the post.
*
* @param uri the full URI input, including the fragment
*/
private void parseFragment(Uri uri) {
// default to do-nothing w.r.t. comments
mDirectOperation = null;
if (uri == null || uri.getFragment() == null) {
return;
}
final String fragment = uri.getFragment();
final Pattern FRAGMENT_COMMENTS_PATTERN = Pattern.compile("comments", Pattern.CASE_INSENSITIVE);
final Pattern FRAGMENT_COMMENT_ID_PATTERN = Pattern.compile("comment-(\\d+)", Pattern.CASE_INSENSITIVE);
final Pattern FRAGMENT_RESPOND_PATTERN = Pattern.compile("respond", Pattern.CASE_INSENSITIVE);
// check for the general "#comments" fragment to jump to the comments section
Matcher commentsMatcher = FRAGMENT_COMMENTS_PATTERN.matcher(fragment);
if (commentsMatcher.matches()) {
mDirectOperation = DirectOperation.COMMENT_JUMP;
mCommentId = 0;
return;
}
// check for the "#respond" fragment to jump to the reply box
Matcher respondMatcher = FRAGMENT_RESPOND_PATTERN.matcher(fragment);
if (respondMatcher.matches()) {
mDirectOperation = DirectOperation.COMMENT_REPLY;
// check whether we are to reply to a specific comment
final String replyToCommentId = uri.getQueryParameter("replytocom");
if (replyToCommentId != null) {
try {
mCommentId = Integer.parseInt(replyToCommentId);
} catch (NumberFormatException e) {
AppLog.e(AppLog.T.UTILS, "replytocom cannot be converted to int" + replyToCommentId, e);
}
}
return;
}
// check for the "#comment-xyz" fragment to jump to a specific comment
Matcher commentIdMatcher = FRAGMENT_COMMENT_ID_PATTERN.matcher(fragment);
if (commentIdMatcher.find() && commentIdMatcher.groupCount() > 0) {
mCommentId = Integer.valueOf(commentIdMatcher.group(1));
mDirectOperation = DirectOperation.COMMENT_JUMP;
}
}
/**
* Parse the URL query parameters and detect attempt to like a post or a comment
*
* @param uri the full URI input, including the query parameters
*/
private void detectLike(Uri uri) {
// check whether we are to like something
final boolean doLike = "1".equals(uri.getQueryParameter("like"));
final String likeActor = uri.getQueryParameter("like_actor");
if (doLike && likeActor != null && likeActor.trim().length() > 0) {
mDirectOperation = DirectOperation.POST_LIKE;
// check whether we are to like a specific comment
final String likeCommentId = uri.getQueryParameter("commentid");
if (likeCommentId != null) {
try {
mCommentId = Integer.parseInt(likeCommentId);
mDirectOperation = DirectOperation.COMMENT_LIKE;
} catch (NumberFormatException e) {
AppLog.e(AppLog.T.UTILS, "commentid cannot be converted to int" + likeCommentId, e);
}
}
}
}
@Override
protected void onResume() {
super.onResume();
EventBus.getDefault().register(this);
if (!hasPagerAdapter() || mBackFromLogin) {
if (isDeepLinking()) {
handleDeepLinking();
}
loadPosts(mBlogId, mPostId);
// clear up the back-from-login flag anyway
mBackFromLogin = false;
}
}
@Override
protected void onPause() {
super.onPause();
EventBus.getDefault().unregister(this);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean hasPagerAdapter() {
return (mViewPager != null && mViewPager.getAdapter() != null);
}
private PostPagerAdapter getPagerAdapter() {
if (mViewPager != null && mViewPager.getAdapter() != null) {
return (PostPagerAdapter) mViewPager.getAdapter();
} else {
return null;
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(ReaderConstants.ARG_IS_SINGLE_POST, mIsSinglePostView);
outState.putBoolean(ReaderConstants.ARG_IS_RELATED_POST, mIsRelatedPostView);
outState.putString(ReaderConstants.ARG_INTERCEPTED_URI, mInterceptedUri);
outState.putSerializable(ReaderConstants.ARG_DIRECT_OPERATION, mDirectOperation);
outState.putInt(ReaderConstants.ARG_COMMENT_ID, mCommentId);
if (hasCurrentTag()) {
outState.putSerializable(ReaderConstants.ARG_TAG, getCurrentTag());
}
if (getPostListType() != null) {
outState.putSerializable(ReaderConstants.ARG_POST_LIST_TYPE, getPostListType());
}
ReaderBlogIdPostId id = getAdapterCurrentBlogIdPostId();
if (id != null) {
outState.putLong(ReaderConstants.ARG_BLOG_ID, id.getBlogId());
outState.putLong(ReaderConstants.ARG_POST_ID, id.getPostId());
}
outState.putBoolean(ReaderConstants.KEY_POST_SLUGS_RESOLUTION_UNDERWAY, mPostSlugsResolutionUnderway);
if (mTrackedPositions.size() > 0) {
outState.putSerializable(ReaderConstants.KEY_TRACKED_POSITIONS, mTrackedPositions);
}
super.onSaveInstanceState(outState);
}
private ReaderBlogIdPostId getAdapterCurrentBlogIdPostId() {
PostPagerAdapter adapter = getPagerAdapter();
if (adapter == null) {
return null;
}
return adapter.getCurrentBlogIdPostId();
}
private ReaderBlogIdPostId getAdapterBlogIdPostIdAtPosition(int position) {
PostPagerAdapter adapter = getPagerAdapter();
if (adapter == null) {
return null;
}
return adapter.getBlogIdPostIdAtPosition(position);
}
@Override
public void onBackPressed() {
ReaderPostDetailFragment fragment = getActiveDetailFragment();
if (fragment != null && fragment.isCustomViewShowing()) {
// if full screen video is showing, hide the custom view rather than navigate back
fragment.hideCustomView();
} else if (fragment != null && fragment.goBackInPostHistory()) {
// noop - fragment moved back to a previous post
} else {
super.onBackPressed();
}
}
/*
* perform analytics tracking and bump the page view for the post at the passed position
* if it hasn't already been done
*/
private void trackPostAtPositionIfNeeded(int position) {
if (!hasPagerAdapter() || mTrackedPositions.contains(position)) return;
ReaderBlogIdPostId idPair = getAdapterBlogIdPostIdAtPosition(position);
if (idPair == null) return;
AppLog.d(AppLog.T.READER, "reader pager > tracking post at position " + position);
mTrackedPositions.add(position);
trackPost(idPair.getBlogId(), idPair.getPostId());
}
/*
* perform analytics tracking and bump the page view for the post
*/
private void trackPost(long blogId, long postId) {
// bump the page view
ReaderPostActions.bumpPageViewForPost(mSiteStore, blogId, postId);
// analytics tracking
AnalyticsUtils.trackWithReaderPostDetails(
AnalyticsTracker.Stat.READER_ARTICLE_OPENED,
ReaderPostTable.getBlogPost(blogId, postId, true));
}
/*
* loads the blogId/postId pairs used to populate the pager adapter - passed blogId/postId will
* be made active after loading unless gotoNext=true, in which case the post after the passed
* one will be made active
*/
private void loadPosts(final long blogId, final long postId) {
new Thread() {
@Override
public void run() {
final ReaderBlogIdPostIdList idList;
if (mIsSinglePostView) {
idList = new ReaderBlogIdPostIdList();
idList.add(new ReaderBlogIdPostId(blogId, postId));
} else {
int maxPosts = ReaderConstants.READER_MAX_POSTS_TO_DISPLAY;
switch (getPostListType()) {
case TAG_FOLLOWED:
case TAG_PREVIEW:
idList = ReaderPostTable.getBlogIdPostIdsWithTag(getCurrentTag(), maxPosts);
break;
case BLOG_PREVIEW:
idList = ReaderPostTable.getBlogIdPostIdsInBlog(blogId, maxPosts);
break;
default:
return;
}
}
final int currentPosition = mViewPager.getCurrentItem();
final int newPosition = idList.indexOf(blogId, postId);
runOnUiThread(new Runnable() {
@Override
public void run() {
AppLog.d(AppLog.T.READER, "reader pager > creating adapter");
PostPagerAdapter adapter =
new PostPagerAdapter(getFragmentManager(), idList);
mViewPager.setAdapter(adapter);
if (adapter.isValidPosition(newPosition)) {
mViewPager.setCurrentItem(newPosition);
trackPostAtPositionIfNeeded(newPosition);
updateTitle(newPosition);
} else if (adapter.isValidPosition(currentPosition)) {
mViewPager.setCurrentItem(currentPosition);
trackPostAtPositionIfNeeded(currentPosition);
updateTitle(currentPosition);
}
// let the user know they can swipe between posts
if (adapter.getCount() > 1 && !AppPrefs.isReaderSwipeToNavigateShown()) {
WPSwipeSnackbar.show(mViewPager);
}
}
});
}
}.start();
}
private ReaderTag getCurrentTag() {
return mCurrentTag;
}
private boolean hasCurrentTag() {
return mCurrentTag != null;
}
private ReaderPostListType getPostListType() {
return mPostListType;
}
private Fragment getActivePagerFragment() {
PostPagerAdapter adapter = getPagerAdapter();
if (adapter == null) {
return null;
}
return adapter.getActiveFragment();
}
private ReaderPostDetailFragment getActiveDetailFragment() {
Fragment fragment = getActivePagerFragment();
if (fragment instanceof ReaderPostDetailFragment) {
return (ReaderPostDetailFragment) fragment;
} else {
return null;
}
}
private Fragment getPagerFragmentAtPosition(int position) {
PostPagerAdapter adapter = getPagerAdapter();
if (adapter == null) {
return null;
}
return adapter.getFragmentAtPosition(position);
}
private ReaderPostDetailFragment getDetailFragmentAtPosition(int position) {
Fragment fragment = getPagerFragmentAtPosition(position);
if (fragment instanceof ReaderPostDetailFragment) {
return (ReaderPostDetailFragment) fragment;
} else {
return null;
}
}
/*
* called when user scrolls towards the last posts - requests older posts with the
* current tag or in the current blog
*/
private void requestMorePosts() {
if (mIsRequestingMorePosts) return;
AppLog.d(AppLog.T.READER, "reader pager > requesting older posts");
switch (getPostListType()) {
case TAG_PREVIEW:
case TAG_FOLLOWED:
ReaderPostService.startServiceForTag(
this,
getCurrentTag(),
ReaderPostService.UpdateAction.REQUEST_OLDER);
break;
case BLOG_PREVIEW:
ReaderPostService.startServiceForBlog(
this,
mBlogId,
ReaderPostService.UpdateAction.REQUEST_OLDER);
break;
}
}
@SuppressWarnings("unused")
public void onEventMainThread(ReaderEvents.UpdatePostsStarted event) {
if (isFinishing()) return;
mIsRequestingMorePosts = true;
mProgress.setVisibility(View.VISIBLE);
}
@SuppressWarnings("unused")
public void onEventMainThread(ReaderEvents.UpdatePostsEnded event) {
if (isFinishing()) return;
PostPagerAdapter adapter = getPagerAdapter();
if (adapter == null) return;
mIsRequestingMorePosts = false;
mProgress.setVisibility(View.GONE);
if (event.getResult() == ReaderActions.UpdateResult.HAS_NEW) {
AppLog.d(AppLog.T.READER, "reader pager > older posts received");
// remember which post to keep active
ReaderBlogIdPostId id = adapter.getCurrentBlogIdPostId();
long blogId = (id != null ? id.getBlogId() : 0);
long postId = (id != null ? id.getPostId() : 0);
loadPosts(blogId, postId);
} else {
AppLog.d(AppLog.T.READER, "reader pager > all posts loaded");
adapter.mAllPostsLoaded = true;
}
}
@SuppressWarnings("unused")
public void onEventMainThread(ReaderEvents.DoSignIn event) {
if (isFinishing()) return;
AnalyticsUtils.trackWithInterceptedUri(AnalyticsTracker.Stat.READER_SIGN_IN_INITIATED, mInterceptedUri);
ActivityLauncher.loginWithoutMagicLink(this);
}
/*
* called by detail fragment to show/hide the toolbar when user scrolls
*/
@Override
public void onShowHideToolbar(boolean show) {
if (!isFinishing()) {
AniUtils.animateTopBar(mToolbar, show);
}
}
/**
* pager adapter containing post detail fragments
**/
private class PostPagerAdapter extends FragmentStatePagerAdapter {
private ReaderBlogIdPostIdList mIdList;
private boolean mAllPostsLoaded;
// this is used to retain created fragments so we can access them in
// getFragmentAtPosition() - necessary because the pager provides no
// built-in way to do this - note that destroyItem() removes fragments
// from this map when they're removed from the adapter, so this doesn't
// retain *every* fragment
private final SparseArray<Fragment> mFragmentMap = new SparseArray<>();
PostPagerAdapter(FragmentManager fm, ReaderBlogIdPostIdList ids) {
super(fm);
mIdList = (ReaderBlogIdPostIdList)ids.clone();
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
// work around "Fragement no longer exists for key" Android bug
// by catching the IllegalStateException
// https://code.google.com/p/android/issues/detail?id=42601
try {
AppLog.d(AppLog.T.READER, "reader pager > adapter restoreState");
super.restoreState(state, loader);
} catch (IllegalStateException e) {
AppLog.e(AppLog.T.READER, e);
}
}
@Override
public Parcelable saveState() {
AppLog.d(AppLog.T.READER, "reader pager > adapter saveState");
return super.saveState();
}
private boolean canRequestMostPosts() {
return !mAllPostsLoaded
&& !mIsSinglePostView
&& (mIdList != null && mIdList.size() < ReaderConstants.READER_MAX_POSTS_TO_DISPLAY)
&& NetworkUtils.isNetworkAvailable(ReaderPostPagerActivity.this);
}
boolean isValidPosition(int position) {
return (position >= 0 && position < getCount());
}
@Override
public int getCount() {
return mIdList.size();
}
@Override
public Fragment getItem(int position) {
if ((position == getCount() - 1) && canRequestMostPosts()) {
requestMorePosts();
}
return ReaderPostDetailFragment.newInstance(
mIsFeed,
mIdList.get(position).getBlogId(),
mIdList.get(position).getPostId(),
mDirectOperation,
mCommentId,
mIsRelatedPostView,
mInterceptedUri,
getPostListType(),
mPostSlugsResolutionUnderway);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object item = super.instantiateItem(container, position);
if (item instanceof Fragment) {
mFragmentMap.put(position, (Fragment) item);
}
return item;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
mFragmentMap.remove(position);
super.destroyItem(container, position, object);
}
private Fragment getActiveFragment() {
return getFragmentAtPosition(mViewPager.getCurrentItem());
}
private Fragment getFragmentAtPosition(int position) {
if (isValidPosition(position)) {
return mFragmentMap.get(position);
} else {
return null;
}
}
private ReaderBlogIdPostId getCurrentBlogIdPostId() {
return getBlogIdPostIdAtPosition(mViewPager.getCurrentItem());
}
ReaderBlogIdPostId getBlogIdPostIdAtPosition(int position) {
if (isValidPosition(position)) {
return mIdList.get(position);
} else {
return null;
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RequestCodes.DO_LOGIN && resultCode == Activity.RESULT_OK) {
mBackFromLogin = true;
}
}
}