package com.leavjenn.hews.ui.post; import android.os.Bundle; import android.support.annotation.NonNull; import android.util.Log; import com.leavjenn.hews.Constants; import com.leavjenn.hews.R; import com.leavjenn.hews.misc.Utils; import com.leavjenn.hews.ui.BasePresenter; import com.leavjenn.hews.misc.SharedPrefsContract; import com.leavjenn.hews.misc.UtilsContract; import com.leavjenn.hews.model.Comment; import com.leavjenn.hews.model.HNItem; import com.leavjenn.hews.model.Post; import com.leavjenn.hews.data.remote.DataManager; import org.parceler.Parcels; import java.util.ArrayList; import java.util.List; import rx.Observable; import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; public class PostPresenter extends BasePresenter { public static final String TAG = "PostPresenter"; static final String KEY_POST_ID_LIST = "key_post_id_list"; static final String KEY_LOADED_POSTS = "key_loaded_posts"; static final String KEY_LOADED_TIME = "key_loaded_time"; static final String KEY_LOADING_STATE = "key_loading_state"; static final String KEY_SEARCH_RESULT_TOTAL_PAGE = "key_search_result_total_page"; static final String KEY_STORY_TYPE = "story_type"; static final String KEY_STORY_TYPE_SPEC = "story_type_spec"; static final String KEY_LIST_STATE = "list_position"; private PostView mPostView; private DataManager mDataManager; private SharedPrefsContract mPrefsManager; private UtilsContract mUtils; private CompositeSubscription mCompositeSubscription; private List<Long> mPostIdList; private ArrayList<Post> mCachedPostList; private int mLoadingState; private Observable<Post> mPostObservable; private String mStoryType; private String mStoryTypeSpec; private int mLoadedTime; private int mSearchResultTotalPages; private boolean mShowPostSummary; public PostPresenter() { mCompositeSubscription = new CompositeSubscription(); mPostIdList = new ArrayList<>(); mCachedPostList = new ArrayList<>(); } public PostPresenter(@NonNull PostView postView) { mPostView = postView; mCompositeSubscription = new CompositeSubscription(); mPostIdList = new ArrayList<>(); mCachedPostList = new ArrayList<>(); } public PostPresenter(@NonNull PostView postView, @NonNull DataManager dataManager, @NonNull SharedPrefsContract prefsManager, @NonNull UtilsContract utils) { this(postView); mDataManager = dataManager; mPrefsManager = prefsManager; mCompositeSubscription = new CompositeSubscription(); mUtils = utils; } public void setView(PostView postView) { mPostView = postView; } public void setDataManager(DataManager dataManager) { mDataManager = dataManager; } public void setPrefsManager(SharedPrefsContract sharedPrefsContract) { mPrefsManager = sharedPrefsContract; } public void setUtils(UtilsContract utils) { mUtils = utils; } public void setStoryType(String storyType) { mStoryType = storyType; } public void setStoryTypeSpec(String storyTypeSpec) { mStoryTypeSpec = storyTypeSpec; } @Override public void setup() { if (mStoryType == null || mStoryTypeSpec == null) { // rare condition: // not new instance, no saved instance state and not popped back stack Log.i("post setup", "null param"); mStoryType = Constants.TYPE_STORY; mStoryTypeSpec = Constants.STORY_TYPE_TOP_PATH; refresh(mStoryType, mStoryTypeSpec); return; } if (mStoryTypeSpec.equals(Constants.TYPE_SEARCH)) { mPostView.showSpinnerPopularDateRange(Integer.valueOf(mStoryTypeSpec.substring(0, 1))); } if (mPostIdList.isEmpty()) { Log.i("post setup", "empty post id list"); refresh(mStoryType, mStoryTypeSpec); } else { Log.i("post setup", "restore posts: " + getCachedPosts().size()); mPostView.restoreCachedPosts(getCachedPosts()); if (mCachedPostList.isEmpty() // post ID list is fetched, but post list is not yet || mLoadingState == Constants.LOADING_IN_PROGRESS) { Log.i("post setup", "reload"); reload(); updateLoadingState(Constants.LOADING_IN_PROGRESS); } } } @Override public void restoreState(Bundle savedInstanceState) { if (savedInstanceState == null) { return; } mStoryType = savedInstanceState.getString(KEY_STORY_TYPE, Constants.TYPE_STORY); mStoryTypeSpec = savedInstanceState.getString(KEY_STORY_TYPE_SPEC, Constants.STORY_TYPE_TOP_PATH); mPostIdList = Parcels.unwrap(savedInstanceState.getParcelable(KEY_POST_ID_LIST)); mCachedPostList = Parcels.unwrap(savedInstanceState.getParcelable(KEY_LOADED_POSTS)); mLoadedTime = savedInstanceState.getInt(KEY_LOADED_TIME); mSearchResultTotalPages = savedInstanceState.getInt(KEY_SEARCH_RESULT_TOTAL_PAGE); mLoadingState = savedInstanceState.getInt(KEY_LOADING_STATE); } @Override public void saveState(Bundle outState) { outState.putParcelable(KEY_POST_ID_LIST, Parcels.wrap(mPostIdList)); outState.putParcelable(KEY_LOADED_POSTS, Parcels.wrap(mCachedPostList)); outState.putInt(KEY_LOADED_TIME, mLoadedTime); outState.putInt(KEY_LOADING_STATE, mLoadingState); outState.putInt(KEY_SEARCH_RESULT_TOTAL_PAGE, mSearchResultTotalPages); outState.putString(KEY_STORY_TYPE, mStoryType); outState.putString(KEY_STORY_TYPE_SPEC, mStoryTypeSpec); } @Override public void destroy() { // if (mCompositeSubscription.hasSubscriptions()) { mCompositeSubscription.clear(); // } mPostView.showInfoLog("destroy", mStoryType + " / " + mStoryTypeSpec); mPostView = null; mDataManager = null; mPrefsManager = null; mUtils = null; } @Override public void unsubscribe() { mCompositeSubscription.clear(); } public void refresh() { refresh(mStoryType, mStoryTypeSpec); } public void refresh(@NonNull String type, @NonNull String spec) { if (!mUtils.isOnline()) { mPostView.hideSwipeRefresh(); mPostView.showOfflineSnackBar(); return; } mStoryType = type; mStoryTypeSpec = spec; mLoadedTime = 1; mPostIdList.clear(); mCachedPostList.clear(); mCompositeSubscription.clear(); mPostView.showSwipeRefresh(); mPostView.hideOfflineSnackBar(); mPostView.resetAdapter(); updateLoadingState(Constants.LOADING_IDLE); switch (mStoryType) { case Constants.TYPE_STORY: loadPostIdList(spec); break; case Constants.TYPE_SEARCH: loadPostIdListBySearch(spec, 0); break; default: mPostView.showErrorLog("refresh", "type"); break; } } public void loadPostIdList(String storyTypeUrl) { mCompositeSubscription.add(mDataManager.getPostList(storyTypeUrl) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<List<Long>>() { @Override public void call(List<Long> longs) { mPostIdList.addAll(longs); loadPosts(mPostIdList.subList(0, Constants.NUM_LOADING_ITEMS), true); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { mPostView.hideSwipeRefresh(); mPostView.showErrorLog("loadPostIdList", throwable.toString()); } })); } public void loadPostIdListBySearch(String timeRangeCombine, int page) { mCompositeSubscription.add( mDataManager.getPopularPosts("created_at_i>" + timeRangeCombine.substring(1, 11) + "," + "created_at_i<" + timeRangeCombine.substring(11), page) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<HNItem.SearchResult>() { @Override public void call(HNItem.SearchResult searchResult) { List<Long> list = new ArrayList<>(); mSearchResultTotalPages = searchResult.getNbPages(); for (int i = 0; i < searchResult.getHits().length; i++) { list.add(searchResult.getHits()[i].getObjectID()); } mPostIdList.clear(); mPostIdList.addAll(list); loadPosts(list, true); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { mPostView.hideSwipeRefresh(); mPostView.showErrorLog("loadPostIdListBySearch", throwable.toString()); } })); } public void loadPosts(List<Long> list, boolean updateObservable) { if (updateObservable || mPostObservable == null) { mPostObservable = mDataManager.getPosts(list).cache(); } mCompositeSubscription.add(mPostObservable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Post>() { @Override public void onCompleted() { updateLoadingState(Constants.LOADING_IDLE); updateCachedPostList(); mPostView.showInfoLog("loadPosts - completed", "cached: " + mCachedPostList.size()); } @Override public void onError(Throwable throwable) { mPostView.hideSwipeRefresh(); mPostView.showErrorLog("loadPosts", throwable.toString()); } @Override public void onNext(Post post) { mPostView.hideSwipeRefresh(); if (post != null) { post.setIndex(mPostView.getLastPostIndex()); Utils.setupPostUrl(post); post.setRead(mPrefsManager.isPostRead(post.getId())); mPostView.showPost(post); if (mLoadingState != Constants.LOADING_IN_PROGRESS) { updateLoadingState(Constants.LOADING_IN_PROGRESS); } if (mShowPostSummary && !mStoryTypeSpec.equals(Constants.STORY_TYPE_ASK_HN_PATH) && !mStoryTypeSpec.equals(Constants.STORY_TYPE_SHOW_HN_PATH) && post.getKids() != null) { loadSummary(post); } } } })); } public void updateCachedPostList() { mCachedPostList.clear(); mCachedPostList.addAll(mPostView.getAllPostList()); } public List<Post> getCachedPosts() { return mCachedPostList; } public void updateLoadingState(int loadingState) { mLoadingState = loadingState; mPostView.updateListFooter(mLoadingState); } public void loadMore() { if (mLoadingState != Constants.LOADING_IDLE) { return; } if (mStoryType.equals(Constants.TYPE_STORY) && Constants.NUM_LOADING_ITEMS * (mLoadedTime + 1) < mPostIdList.size()) { int start = Constants.NUM_LOADING_ITEMS * mLoadedTime, end = Constants.NUM_LOADING_ITEMS * (++mLoadedTime); updateLoadingState(Constants.LOADING_IN_PROGRESS); loadPosts(mPostIdList.subList(start, end), true); mPostView.showInfoLog("loading story", String.valueOf(start) + " - " + end); } else if (mStoryType.equals(Constants.TYPE_SEARCH) && mLoadedTime < mSearchResultTotalPages) { mPostView.showInfoLog("loading pop", String.valueOf(mLoadedTime) + "/" + String.valueOf(mSearchResultTotalPages)); updateLoadingState(Constants.LOADING_IN_PROGRESS); loadPostIdListBySearch(mStoryTypeSpec, mLoadedTime++); } else { mPostView.showLongToast(R.string.no_more_posts_prompt); updateLoadingState(Constants.LOADING_FINISH); } } public void reload() { if (mStoryType.equals(Constants.TYPE_STORY) && Constants.NUM_LOADING_ITEMS * mLoadedTime < mPostIdList.size()) { int start = Constants.NUM_LOADING_ITEMS * (mLoadedTime - 1), end = Constants.NUM_LOADING_ITEMS * mLoadedTime; loadPosts(mPostIdList.subList(start, end), false); mPostView.showInfoLog("reload story", String.valueOf(start) + "-" + end); } else if (mStoryType.equals(Constants.TYPE_SEARCH) && mLoadedTime < mSearchResultTotalPages) { mPostView.showInfoLog("reload pop", String.valueOf(mLoadedTime) + "/" + String.valueOf(mSearchResultTotalPages)); loadPosts(mPostIdList, false); } else { mPostView.showLongToast(R.string.no_more_posts_prompt); updateLoadingState(Constants.LOADING_FINISH); } } void loadSummary(final Post post) { mCompositeSubscription.add(mDataManager.getSummary(post.getKids()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<Comment>() { @Override public void call(Comment comment) { if (comment != null) { post.setSummary(mUtils.convertHtmlToString( comment.getText().replace("<p>", "<br /><br />").replace("\n", "<br />"))); mPostView.showSummary(post.getIndex()); } else { post.setSummary(null); } } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { mPostView.showErrorLog("loadSummary: " + String.valueOf(post.getId()), throwable.toString()); } })); } public void setShowPostSummaryPref() { if (mPrefsManager == null) { Log.e("postpresenter", "null mPrefsManager"); mShowPostSummary = false; } mShowPostSummary = mPrefsManager.isShowPostSummary(); } public void refreshPostSummaryPref() { if (mShowPostSummary != mPrefsManager.isShowPostSummary()) { refresh(mStoryType, mStoryTypeSpec); } } }