diff options
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostPagerActivity.java')
-rw-r--r-- | WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostPagerActivity.java | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostPagerActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostPagerActivity.java new file mode 100644 index 000000000..5d2d4cbe5 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostPagerActivity.java @@ -0,0 +1,532 @@ +package org.wordpress.android.ui.reader; + +import android.app.Fragment; +import android.app.FragmentManager; +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.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.analytics.AnalyticsTracker; +import org.wordpress.android.datasets.ReaderPostTable; +import org.wordpress.android.models.ReaderTag; +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.widgets.WPViewPager; + +import java.util.HashSet; + +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 + */ +public class ReaderPostPagerActivity extends AppCompatActivity + implements ReaderInterfaces.AutoHideToolbarListener { + + private WPViewPager mViewPager; + private ProgressBar mProgress; + private Toolbar mToolbar; + + private ReaderTag mCurrentTag; + private long mBlogId; + private long mPostId; + private int mLastSelectedPosition = -1; + private ReaderPostListType mPostListType; + + private boolean mIsRequestingMorePosts; + private boolean mIsSinglePostView; + private boolean mIsRelatedPostView; + + private final HashSet<Integer> mTrackedPositions = new HashSet<>(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + 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) { + mBlogId = savedInstanceState.getLong(ReaderConstants.ARG_BLOG_ID); + mPostId = savedInstanceState.getLong(ReaderConstants.ARG_POST_ID); + mIsSinglePostView = savedInstanceState.getBoolean(ReaderConstants.ARG_IS_SINGLE_POST); + mIsRelatedPostView = savedInstanceState.getBoolean(ReaderConstants.ARG_IS_RELATED_POST); + 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); + } + } else { + mBlogId = getIntent().getLongExtra(ReaderConstants.ARG_BLOG_ID, 0); + mPostId = getIntent().getLongExtra(ReaderConstants.ARG_POST_ID, 0); + mIsSinglePostView = getIntent().getBooleanExtra(ReaderConstants.ARG_IS_SINGLE_POST, false); + mIsRelatedPostView = getIntent().getBooleanExtra(ReaderConstants.ARG_IS_RELATED_POST, false); + 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; + } + + setTitle(mIsRelatedPostView ? R.string.reader_title_related_post_detail : R.string.reader_title_post_detail); + + // 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_close_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); + + // pause the previous web view - important because otherwise embedded content + // will continue to play + if (mLastSelectedPosition > -1 && mLastSelectedPosition != position) { + ReaderPostDetailFragment lastFragment = getDetailFragmentAtPosition(mLastSelectedPosition); + if (lastFragment != null) { + lastFragment.pauseWebView(); + } + } + + // resume the newly active webView if it was previously paused + ReaderPostDetailFragment thisFragment = getDetailFragmentAtPosition(position); + if (thisFragment != null) { + thisFragment.resumeWebViewIfPaused(); + } + + mLastSelectedPosition = position; + } + }); + + mViewPager.setPageTransformer(false, + new ReaderViewPagerTransformer(ReaderViewPagerTransformer.TransformType.SLIDE_OVER)); + } + + @Override + protected void onResume() { + super.onResume(); + EventBus.getDefault().register(this); + if (!hasPagerAdapter()) { + loadPosts(mBlogId, mPostId); + } + } + + @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); + + 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()); + } + + 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); + + // bump the page view + ReaderPostActions.bumpPageViewForPost(idPair.getBlogId(), idPair.getPostId()); + + // analytics tracking + AnalyticsUtils.trackWithReaderPostDetails( + AnalyticsTracker.Stat.READER_ARTICLE_OPENED, + ReaderPostTable.getPost(idPair.getBlogId(), idPair.getPostId(), 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); + } else if (adapter.isValidPosition(currentPosition)) { + mViewPager.setCurrentItem(currentPosition); + trackPostAtPositionIfNeeded(currentPosition); + } + } + }); + } + }.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; + } + } + + /* + * 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 = new ReaderBlogIdPostIdList(); + 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.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( + mIdList.get(position).getBlogId(), + mIdList.get(position).getPostId(), + mIsRelatedPostView, + getPostListType()); + } + + @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; + } + } + } +} |