From 45f67457fa6bf49fdf2648e1dc3a3802beac41b4 Mon Sep 17 00:00:00 2001 From: Alex Ning Date: Thu, 27 Dec 2018 11:29:02 +0800 Subject: [PATCH] Use Android Paging Library to load and display best posts. Loading best posts in a specific subreddit is broken. Fixed a bug which is PostViewModel's data becoming null after the app restart. Add notch support for Android Pie. --- .idea/caches/build_file_checksums.ser | Bin 533 -> 533 bytes .idea/misc.xml | 2 +- app/build.gradle | 5 + .../infinityforreddit/NetworkState.java | 34 +++ .../infinityforreddit/PostDataSource.java | 124 +++++++++++ .../PostDataSourceFactory.java | 37 ++++ .../infinityforreddit/PostFragment.java | 201 ++---------------- .../PostPaginationScrollListener.java | 197 ----------------- .../PostRecyclerViewAdapter.java | 194 +++++++++-------- .../infinityforreddit/PostViewModel.java | 66 ++++-- .../ViewPostDetailActivity.java | 34 --- app/src/main/res/values-v28/styles.xml | 12 ++ build.gradle | 3 + 13 files changed, 389 insertions(+), 520 deletions(-) create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/NetworkState.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSourceFactory.java delete mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/PostPaginationScrollListener.java create mode 100644 app/src/main/res/values-v28/styles.xml diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index a7bbc0e3a0c321896c3530975ec13adcf767f0f2..dd278ac49d699d5bfd2676624d188017723eb5a4 100644 GIT binary patch delta 53 zcmV-50LuTB1eFAkm;~*lNKlcSpAh(A*}7f`zRct7#qTp4ly{R$0l5(zN@QfqR!N~! LaG{}L!QUcyv+fz4 delta 53 zcmV-50LuTB1eFAkm;})}y84lvpAdk$&Bm6(l9S$L?4d%P$0?Ib0l5(vGUI0hTl-<8 LiZN<;`NU&*)O{MV diff --git a/.idea/misc.xml b/.idea/misc.xml index dc345694..cc51e58e 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -25,7 +25,7 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 317de2a4..c9d61af9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,6 +16,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + } } repositories { @@ -66,4 +70,5 @@ dependencies { annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' implementation 'com.github.santalu:aspect-ratio-imageview:1.0.6' implementation 'com.felipecsl:gifimageview:2.2.0' + implementation "android.arch.paging:runtime:1.0.1" } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/NetworkState.java b/app/src/main/java/ml/docilealligator/infinityforreddit/NetworkState.java new file mode 100644 index 00000000..495f54c2 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/NetworkState.java @@ -0,0 +1,34 @@ +package ml.docilealligator.infinityforreddit; + +class NetworkState { + public enum Status{ + RUNNING, + SUCCESS, + FAILED + } + + + private final Status status; + private final String msg; + + static final NetworkState LOADED; + static final NetworkState LOADING; + + NetworkState(Status status, String msg) { + this.status = status; + this.msg = msg; + } + + static { + LOADED=new NetworkState(Status.SUCCESS,"Success"); + LOADING=new NetworkState(Status.RUNNING,"Running"); + } + + public Status getStatus() { + return status; + } + + public String getMsg() { + return msg; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java new file mode 100644 index 00000000..41ba65d1 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java @@ -0,0 +1,124 @@ +package ml.docilealligator.infinityforreddit; + +import android.arch.lifecycle.MutableLiveData; +import android.arch.paging.PageKeyedDataSource; +import android.support.annotation.NonNull; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Locale; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Retrofit; + +class PostDataSource extends PageKeyedDataSource { + private Retrofit retrofit; + private String accessToken; + private Locale locale; + private boolean isBestPost; + + //private MutableLiveData networkState; + private MutableLiveData initialLoading; + + PostDataSource(Retrofit retrofit, String accessToken, Locale locale, boolean isBestPost) { + this.retrofit = retrofit; + this.accessToken = accessToken; + this.locale = locale; + this.isBestPost = isBestPost; + //networkState = new MutableLiveData(); + initialLoading = new MutableLiveData(); + + } + + /*public MutableLiveData getNetworkState() { + return networkState; + }*/ + + public MutableLiveData getInitialLoading() { + return initialLoading; + } + + @Override + public void loadInitial(@NonNull LoadInitialParams params, @NonNull final LoadInitialCallback callback) { + initialLoading.postValue(NetworkState.LOADING); + //networkState.postValue(NetworkState.LOADING); + + RedditAPI api = retrofit.create(RedditAPI.class); + + Call bestPost = api.getBestPost(null, RedditUtils.getOAuthHeader(accessToken)); + bestPost.enqueue(new Callback() { + @Override + public void onResponse(Call call, retrofit2.Response response) { + if (response.isSuccessful()) { + ParsePost.parsePost(response.body(), locale, + new ParsePost.ParsePostListener() { + @Override + public void onParsePostSuccess(ArrayList newPosts, String lastItem) { + callback.onResult(newPosts, null, lastItem); + initialLoading.postValue(NetworkState.LOADED); + //networkState.postValue(NetworkState.LOADED); + } + + @Override + public void onParsePostFail() { + Log.i("Post fetch error", "Error parsing data"); + } + }); + } else { + Log.i("Post fetch error", response.message()); + initialLoading.postValue(new NetworkState(NetworkState.Status.FAILED, response.message())); + //networkState.postValue(new NetworkState(NetworkState.Status.FAILED, response.message())); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + String errorMessage = t == null ? "unknown error" : t.getMessage(); + //networkState.postValue(new NetworkState(NetworkState.Status.FAILED, errorMessage)); + } + }); + } + + @Override + public void loadBefore(@NonNull LoadParams params, @NonNull LoadCallback callback) { + + } + + @Override + public void loadAfter(@NonNull LoadParams params, @NonNull final LoadCallback callback) { + //networkState.postValue(NetworkState.LOADING); + + RedditAPI api = retrofit.create(RedditAPI.class); + Call bestPost = api.getBestPost(params.key, RedditUtils.getOAuthHeader(accessToken)); + + bestPost.enqueue(new Callback() { + @Override + public void onResponse(Call call, retrofit2.Response response) { + if(response.isSuccessful()) { + ParsePost.parsePost(response.body(), locale, new ParsePost.ParsePostListener() { + @Override + public void onParsePostSuccess(ArrayList newPosts, String lastItem) { + callback.onResult(newPosts, lastItem); + //networkState.postValue(NetworkState.LOADED); + } + + @Override + public void onParsePostFail() { + Log.i("Best post", "Error parsing data"); + } + }); + } else { + Log.i("best post", response.message()); + //networkState.postValue(new NetworkState(NetworkState.Status.FAILED, response.message())); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + String errorMessage = t == null ? "unknown error" : t.getMessage(); + //networkState.postValue(new NetworkState(NetworkState.Status.FAILED, errorMessage)); + } + }); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSourceFactory.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSourceFactory.java new file mode 100644 index 00000000..20cda5ca --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSourceFactory.java @@ -0,0 +1,37 @@ +package ml.docilealligator.infinityforreddit; + +import android.arch.lifecycle.MutableLiveData; +import android.arch.paging.DataSource; +import android.arch.paging.PageKeyedDataSource; + +import java.util.Locale; + +import retrofit2.Retrofit; + +class PostDataSourceFactory extends DataSource.Factory { + private Retrofit retrofit; + private String accessToken; + private Locale locale; + private boolean isBestPost; + + private MutableLiveData> mutableLiveData; + + PostDataSourceFactory(Retrofit retrofit, String accessToken, Locale locale, boolean isBestPost) { + this.retrofit = retrofit; + this.accessToken = accessToken; + this.locale = locale; + this.isBestPost = isBestPost; + mutableLiveData = new MutableLiveData<>(); + } + + @Override + public DataSource create() { + PostDataSource postDataSource = new PostDataSource(retrofit, accessToken, locale, isBestPost); + mutableLiveData.postValue(postDataSource); + return postDataSource; + } + + public MutableLiveData> getMutableLiveData() { + return mutableLiveData; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostFragment.java index e3a2ba5a..52c92813 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostFragment.java @@ -1,19 +1,15 @@ package ml.docilealligator.infinityforreddit; -import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.Snackbar; import android.support.v4.app.Fragment; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -21,15 +17,9 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; -import com.bumptech.glide.Glide; - -import java.util.ArrayList; - import javax.inject.Inject; import javax.inject.Named; -import retrofit2.Call; -import retrofit2.Callback; import retrofit2.Retrofit; @@ -53,9 +43,6 @@ public class PostFragment extends Fragment implements FragmentCommunicator { private LinearLayout mFetchPostErrorLinearLayout; private ImageView mFetchPostErrorImageView; - private String mLastItem; - private PaginationSynchronizer mPaginationSynchronizer; - private boolean mIsBestPost; private String mSubredditName; @@ -76,14 +63,6 @@ public class PostFragment extends Fragment implements FragmentCommunicator { // Required empty public constructor } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(LAST_ITEM_STATE, mLastItem); - outState.putBoolean(LOADING_STATE_STATE, mPaginationSynchronizer.isLoading()); - outState.putBoolean(LOAD_SUCCESS_STATE, mPaginationSynchronizer.isLoadingMorePostsSuccess()); - } - @Override public void onResume() { super.onResume(); @@ -117,10 +96,11 @@ public class PostFragment extends Fragment implements FragmentCommunicator { });*/ mIsBestPost = getArguments().getBoolean(IS_BEST_POST_KEY); + if(!mIsBestPost) { mSubredditName = getArguments().getString(SUBREDDIT_NAME_KEY); } else { - mFetchPostErrorLinearLayout.setOnClickListener(new View.OnClickListener() { + /*mFetchPostErrorLinearLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(mIsBestPost) { @@ -129,159 +109,35 @@ public class PostFragment extends Fragment implements FragmentCommunicator { fetchPost(); } } - }); + });*/ } - mPaginationSynchronizer = new PaginationSynchronizer(); - mPaginationSynchronizer.addLastItemSynchronizer(new LastItemSynchronizer() { - @Override - public void lastItemChanged(String lastItem) { - mLastItem = lastItem; - } - }); - - mPostViewModel = ViewModelProviders.of(this).get(PostViewModel.class); - mPostViewModel.getPosts().observe(this, new Observer>() { - @Override - public void onChanged(@Nullable ArrayList posts) { - if(posts == null) { - Log.i("datachange", Integer.toString(0)); - } else { - Log.i("datachange", Integer.toString(posts.size())); - mAdapter.changeDataSet(posts); - } - } - }); - if(mIsBestPost) { mAdapter = new PostRecyclerViewAdapter(getActivity(), mOauthRetrofit, - mSharedPreferences, mPaginationSynchronizer, mIsBestPost); - - mPostRecyclerView.addOnScrollListener(new PostPaginationScrollListener( - getActivity(), mOauthRetrofit, mPostViewModel, mLinearLayoutManager, - mLastItem, mPaginationSynchronizer, mSubredditName, mIsBestPost, - mPaginationSynchronizer.isLoading(), mPaginationSynchronizer.isLoadingMorePostsSuccess(), - getResources().getConfiguration().locale)); + mSharedPreferences, mIsBestPost); } else { mAdapter = new PostRecyclerViewAdapter(getActivity(), mRetrofit, - mSharedPreferences, mPaginationSynchronizer, mIsBestPost); - - mPostRecyclerView.addOnScrollListener(new PostPaginationScrollListener( - getActivity(), mRetrofit, mPostViewModel, mLinearLayoutManager, - mLastItem, mPaginationSynchronizer, mSubredditName, mIsBestPost, - mPaginationSynchronizer.isLoading(), mPaginationSynchronizer.isLoadingMorePostsSuccess(), - getResources().getConfiguration().locale)); + mSharedPreferences, mIsBestPost); } mPostRecyclerView.setAdapter(mAdapter); - if(savedInstanceState != null && savedInstanceState.containsKey(LAST_ITEM_STATE)) { - mLastItem = savedInstanceState.getString(LAST_ITEM_STATE); + String accessToken = getActivity().getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE) + .getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, ""); - mPaginationSynchronizer.notifyLastItemChanged(mLastItem); - mPaginationSynchronizer.setLoadSuccess(savedInstanceState.getBoolean(LOAD_SUCCESS_STATE)); - mPaginationSynchronizer.setLoadingState(savedInstanceState.getBoolean(LOADING_STATE_STATE)); - - mProgressBar.setVisibility(View.GONE); - } else { - if(mIsBestPost) { - fetchBestPost(); - } else { - fetchPost(); - } - } + PostViewModel.Factory factory = new PostViewModel.Factory(mOauthRetrofit, accessToken, + getResources().getConfiguration().locale, mIsBestPost); + mPostViewModel = ViewModelProviders.of(this, factory).get(PostViewModel.class); + mPostViewModel.getPosts().observe(this, posts -> mAdapter.submitList(posts)); return rootView; } - private void fetchBestPost() { - mFetchPostErrorLinearLayout.setVisibility(View.GONE); - mProgressBar.setVisibility(View.VISIBLE); + @Override + public void refresh() { - RedditAPI api = mOauthRetrofit.create(RedditAPI.class); - - String accessToken = getActivity().getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE) - .getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, ""); - Call bestPost = api.getBestPost(mLastItem, RedditUtils.getOAuthHeader(accessToken)); - bestPost.enqueue(new Callback() { - @Override - public void onResponse(Call call, retrofit2.Response response) { - if(getActivity() != null) { - if(response.isSuccessful()) { - ParsePost.parsePost(response.body(), getResources().getConfiguration().locale, - new ParsePost.ParsePostListener() { - @Override - public void onParsePostSuccess(ArrayList newPosts, String lastItem) { - if(isAdded() && getActivity() != null) { - mLastItem = lastItem; - mPaginationSynchronizer.notifyLastItemChanged(lastItem); - mPostViewModel.setPosts(newPosts); - mProgressBar.setVisibility(View.GONE); - } - } - - @Override - public void onParsePostFail() { - Log.i("Post fetch error", "Error parsing data"); - showErrorView(); - } - }); - } else { - Log.i("Post fetch error", response.message()); - showErrorView(); - } - } - } - - @Override - public void onFailure(Call call, Throwable t) { - showErrorView(); - } - }); - } - - private void fetchPost() { - mFetchPostErrorLinearLayout.setVisibility(View.GONE); - mProgressBar.setVisibility(View.VISIBLE); - - RedditAPI api = mRetrofit.create(RedditAPI.class); - Call getPost = api.getPost(mSubredditName, mLastItem); - getPost.enqueue(new Callback() { - @Override - public void onResponse(Call call, retrofit2.Response response) { - if(getActivity() != null) { - if(response.isSuccessful()) { - ParsePost.parsePost(response.body(), getResources().getConfiguration().locale, - new ParsePost.ParsePostListener() { - @Override - public void onParsePostSuccess(ArrayList newPosts, String lastItem) { - if(isAdded() && getActivity() != null) { - mLastItem = lastItem; - mPaginationSynchronizer.notifyLastItemChanged(lastItem); - mPostViewModel.setPosts(newPosts); - mProgressBar.setVisibility(View.GONE); - } - } - - @Override - public void onParsePostFail() { - Log.i("Post fetch error", "Error parsing data"); - showErrorView(); - } - }); - } else { - Log.i("Post fetch error", response.message()); - showErrorView(); - } - } - } - - @Override - public void onFailure(Call call, Throwable t) { - showErrorView(); - } - }); } + /* private void showErrorView() { mProgressBar.setVisibility(View.GONE); if(mIsBestPost) { @@ -303,32 +159,5 @@ public class PostFragment extends Fragment implements FragmentCommunicator { }); snackbar.show(); } - } - - @Override - public void refresh() { - mLastItem = null; - mPostRecyclerView.clearOnScrollListeners(); - mPostRecyclerView.getRecycledViewPool().clear(); - - mPostViewModel.setPosts(null); - - if(mIsBestPost) { - mPostRecyclerView.addOnScrollListener(new PostPaginationScrollListener( - getActivity(), mOauthRetrofit, mPostViewModel, mLinearLayoutManager, - mLastItem, mPaginationSynchronizer, mSubredditName, mIsBestPost, - mPaginationSynchronizer.isLoading(), mPaginationSynchronizer.isLoadingMorePostsSuccess(), - getResources().getConfiguration().locale)); - - fetchBestPost(); - } else { - mPostRecyclerView.addOnScrollListener(new PostPaginationScrollListener( - getActivity(), mRetrofit, mPostViewModel, mLinearLayoutManager, - mLastItem, mPaginationSynchronizer, mSubredditName, mIsBestPost, - mPaginationSynchronizer.isLoading(), mPaginationSynchronizer.isLoadingMorePostsSuccess(), - getResources().getConfiguration().locale)); - - fetchPost(); - } - } + }*/ } \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostPaginationScrollListener.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostPaginationScrollListener.java deleted file mode 100644 index 0c11e305..00000000 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostPaginationScrollListener.java +++ /dev/null @@ -1,197 +0,0 @@ -package ml.docilealligator.infinityforreddit; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.widget.Toast; - -import java.util.ArrayList; -import java.util.Locale; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Retrofit; - -/** - * Created by alex on 3/12/18. - */ - -class PostPaginationScrollListener extends RecyclerView.OnScrollListener { - private Context mContext; - private Retrofit mRetrofit; - private PostViewModel mPostViewModel; - private LinearLayoutManager mLayoutManager; - private PaginationSynchronizer mPaginationSynchronizer; - - private String mSubredditName; - private boolean isBestPost; - private boolean isLoading; - private boolean loadSuccess; - private Locale locale; - private String mLastItem; - - PostPaginationScrollListener(Context context, Retrofit retrofit, PostViewModel postViewModel, - LinearLayoutManager layoutManager, String lastItem, - PaginationSynchronizer paginationSynchronizer, final String subredditName, - final boolean isBestPost, boolean isLoading, boolean loadSuccess, Locale locale) { - if(context != null) { - mContext = context; - mRetrofit = retrofit; - mPostViewModel = postViewModel; - mLayoutManager = layoutManager; - mLastItem = lastItem; - mPaginationSynchronizer = paginationSynchronizer; - mSubredditName = subredditName; - this.isBestPost = isBestPost; - this.isLoading = isLoading; - this.loadSuccess = loadSuccess; - this.locale = locale; - - PaginationRetryNotifier paginationRetryNotifier = new PaginationRetryNotifier() { - @Override - public void retry() { - if (isBestPost) { - fetchBestPost(); - } else { - fetchPost(subredditName); - } - } - }; - mPaginationSynchronizer.setPaginationRetryNotifier(paginationRetryNotifier); - mPaginationSynchronizer.addLastItemSynchronizer(new LastItemSynchronizer() { - @Override - public void lastItemChanged(String lastItem) { - mLastItem = lastItem; - } - }); - } - } - - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - if(!isLoading && loadSuccess) { - int visibleItemCount = mLayoutManager.getChildCount(); - int totalItemCount = mLayoutManager.getItemCount(); - int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition(); - - if((visibleItemCount + firstVisibleItemPosition >= totalItemCount) && firstVisibleItemPosition >= 0) { - if(isBestPost) { - fetchBestPost(); - } else { - fetchPost(mSubredditName); - } - } - } - } - - - private void fetchBestPost() { - isLoading = true; - loadSuccess = false; - mPaginationSynchronizer.setLoadingState(true); - - RedditAPI api = mRetrofit.create(RedditAPI.class); - - String accessToken = mContext.getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE) - .getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, ""); - Call bestPost = api.getBestPost(mLastItem, RedditUtils.getOAuthHeader(accessToken)); - bestPost.enqueue(new Callback() { - @Override - public void onResponse(Call call, retrofit2.Response response) { - if(response.isSuccessful()) { - ParsePost.parsePost(response.body(), locale, new ParsePost.ParsePostListener() { - @Override - public void onParsePostSuccess(ArrayList newPosts, String lastItem) { - if(mPostViewModel != null) { - mPostViewModel.addPosts(newPosts); - - mLastItem = lastItem; - mPaginationSynchronizer.notifyLastItemChanged(lastItem); - - isLoading = false; - loadSuccess = true; - mPaginationSynchronizer.setLoadingState(false); - mPaginationSynchronizer.loadSuccess(true); - } - } - - @Override - public void onParsePostFail() { - Toast.makeText(mContext, "Error parsing data", Toast.LENGTH_SHORT).show(); - Log.i("Best post", "Error parsing data"); - loadFailed(); - } - }); - } else { - Toast.makeText(mContext, "Error getting best post", Toast.LENGTH_SHORT).show(); - Log.i("best post", response.message()); - loadFailed(); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - loadFailed(); - } - }); - } - - private void fetchPost(final String subredditName) { - isLoading = true; - loadSuccess = false; - mPaginationSynchronizer.setLoadingState(true); - - RedditAPI api = mRetrofit.create(RedditAPI.class); - Call getPost = api.getPost(subredditName, mLastItem); - getPost.enqueue(new Callback() { - @Override - public void onResponse(Call call, retrofit2.Response response) { - if(response.isSuccessful()) { - ParsePost.parsePost(response.body(), locale, new ParsePost.ParsePostListener() { - @Override - public void onParsePostSuccess(ArrayList newPosts, String lastItem) { - if(mPostViewModel != null) { - mPostViewModel.addPosts(newPosts); - - mLastItem = lastItem; - mPaginationSynchronizer.notifyLastItemChanged(lastItem); - - isLoading = false; - loadSuccess = true; - mPaginationSynchronizer.setLoadingState(false); - mPaginationSynchronizer.loadSuccess(true); - } - } - - @Override - public void onParsePostFail() { - Toast.makeText(mContext, "Error parsing data", Toast.LENGTH_SHORT).show(); - Log.i("Best post", "Error parsing data"); - loadFailed(); - } - }); - } else { - Toast.makeText(mContext, "Error getting best post", Toast.LENGTH_SHORT).show(); - Log.i("best post", response.message()); - loadFailed(); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(mContext, "Error getting best post", Toast.LENGTH_SHORT).show(); - loadFailed(); - } - }); - } - - private void loadFailed() { - isLoading = false; - loadSuccess = false; - mPaginationSynchronizer.setLoadingState(false); - mPaginationSynchronizer.loadSuccess(false); - } -} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java index 66c29d65..2d6f54aa 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java @@ -1,5 +1,6 @@ package ml.docilealligator.infinityforreddit; +import android.arch.paging.PagedListAdapter; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -11,6 +12,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.customtabs.CustomTabsIntent; import android.support.v4.content.ContextCompat; +import android.support.v7.util.DiffUtil; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -34,7 +36,7 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.Target; -import java.util.ArrayList; +import java.util.List; import CustomView.AspectRatioGifImageView; import butterknife.BindView; @@ -47,12 +49,10 @@ import retrofit2.Retrofit; * Created by alex on 2/25/18. */ -class PostRecyclerViewAdapter extends RecyclerView.Adapter { - private ArrayList mPostData; +class PostRecyclerViewAdapter extends PagedListAdapter { private Context mContext; private Retrofit mOauthRetrofit; private SharedPreferences mSharedPreferences; - private PaginationSynchronizer mPaginationSynchronizer; private RequestManager glide; private SubredditDao subredditDao; private boolean isLoadingMorePostSuccess = true; @@ -62,21 +62,32 @@ class PostRecyclerViewAdapter extends RecyclerView.Adapter(); - mPaginationSynchronizer = paginationSynchronizer; this.hasMultipleSubreddits = hasMultipleSubreddits; glide = Glide.with(mContext.getApplicationContext()); subredditDao = SubredditRoomDatabase.getDatabase(mContext.getApplicationContext()).subredditDao(); } } + static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull Post post, @NonNull Post t1) { + return post.getId().equals(t1.getId()); + } + + @Override + public boolean areContentsTheSame(@NonNull Post post, @NonNull Post t1) { + return post.getTitle().equals(t1.getTitle()); + } + }; + void setCanStartActivity(boolean canStartActivity) { this.canStartActivity = canStartActivity; } @@ -93,27 +104,33 @@ class PostRecyclerViewAdapter extends RecyclerView.Adapter payloads) { + onBindViewHolder(holder, position); + } + @Override public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) { if(holder instanceof DataViewHolder) { - if(mPostData.get(holder.getAdapterPosition()) == null) { + Post post = getItem(position); + if(post == null) { Log.i("is null", Integer.toString(holder.getAdapterPosition())); } else { - final String id = mPostData.get(holder.getAdapterPosition()).getFullName(); - final String subredditName = mPostData.get(holder.getAdapterPosition()).getSubredditNamePrefixed(); - final String postTime = mPostData.get(holder.getAdapterPosition()).getPostTime(); - final String title = mPostData.get(holder.getAdapterPosition()).getTitle(); - final String permalink = mPostData.get(holder.getAdapterPosition()).getPermalink(); - int voteType = mPostData.get(holder.getAdapterPosition()).getVoteType(); - int gilded = mPostData.get(holder.getAdapterPosition()).getGilded(); - boolean nsfw = mPostData.get(holder.getAdapterPosition()).isNSFW(); + final String id = post.getFullName(); + final String subredditName = post.getSubredditNamePrefixed(); + final String postTime = post.getPostTime(); + final String title = post.getTitle(); + final String permalink = post.getPermalink(); + int voteType = post.getVoteType(); + int gilded = post.getGilded(); + boolean nsfw = post.isNSFW(); - if(mPostData.get(holder.getAdapterPosition()).getSubredditIconUrl() == null) { + if(post.getSubredditIconUrl() == null) { new LoadSubredditIconAsyncTask(subredditDao, subredditName, new LoadSubredditIconAsyncTask.LoadSubredditIconAsyncTaskListener() { @Override public void loadIconSuccess(String iconImageUrl) { - if(mContext != null && !mPostData.isEmpty()) { + if(mContext != null && getItemCount() > 0) { if(!iconImageUrl.equals("")) { glide.load(iconImageUrl) .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) @@ -142,13 +159,13 @@ class PostRecyclerViewAdapter extends RecyclerView.Adapter= 0) { - mPostData.get(holder.getAdapterPosition()).setSubredditIconUrl(iconImageUrl); + post.setSubredditIconUrl(iconImageUrl); } } } }).execute(); - } else if(!mPostData.get(position).getSubredditIconUrl().equals("")) { - glide.load(mPostData.get(position).getSubredditIconUrl()) + } else if(!post.getSubredditIconUrl().equals("")) { + glide.load(post.getSubredditIconUrl()) .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) .error(glide.load(R.drawable.subreddit_default_icon)) .listener(new RequestListener() { @@ -181,7 +198,7 @@ class PostRecyclerViewAdapter extends RecyclerView.Adapter 0) { ((DataViewHolder) holder).gildedImageView.setVisibility(View.VISIBLE); @@ -247,29 +264,29 @@ class PostRecyclerViewAdapter extends RecyclerView.Adapter= mPostData.size() ? VIEW_TYPE_LOADING : VIEW_TYPE_DATA); + return (position >= getItemCount() ? VIEW_TYPE_LOADING : VIEW_TYPE_DATA); } - void changeDataSet(ArrayList posts) { - mPostData = posts; - if(dataSize == 0 || posts.size() <= dataSize) { - notifyDataSetChanged(); - } else { - notifyItemRangeInserted(dataSize, posts.size() - dataSize); + private boolean hasExtraRow() { + return networkState != null && networkState != NetworkState.LOADED; + } + + public void setNetworkState(NetworkState newNetworkState) { + NetworkState previousState = this.networkState; + boolean previousExtraRow = hasExtraRow(); + this.networkState = newNetworkState; + boolean newExtraRow = hasExtraRow(); + if (previousExtraRow != newExtraRow) { + if (previousExtraRow) { + notifyItemRemoved(getItemCount()); + } else { + notifyItemInserted(getItemCount()); + } + } else if (newExtraRow && previousState != newNetworkState) { + notifyItemChanged(getItemCount() - 1); } - dataSize = posts.size(); } class DataViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostViewModel.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostViewModel.java index af6c1f31..0be971eb 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostViewModel.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostViewModel.java @@ -1,30 +1,66 @@ package ml.docilealligator.infinityforreddit; import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.MutableLiveData; import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProvider; +import android.arch.paging.LivePagedListBuilder; +import android.arch.paging.PageKeyedDataSource; +import android.arch.paging.PagedList; +import android.support.annotation.NonNull; -import java.util.ArrayList; +import java.util.Locale; +import java.util.concurrent.Executor; + +import retrofit2.Retrofit; public class PostViewModel extends ViewModel { - private MutableLiveData> posts = new MutableLiveData<>(); + private Executor executor; + LiveData networkState; + LiveData> posts; + LiveData> liveDataSource; - LiveData> getPosts() { - if(posts == null) { - setPosts(new ArrayList()); - } + public PostViewModel(Retrofit retrofit, String accessToken, Locale locale, boolean isBestPost) { + //executor = Executors.newFixedThreadPool(5); + + PostDataSourceFactory postDataSourceFactory = new PostDataSourceFactory(retrofit, accessToken, locale, isBestPost); + /*networkState = Transformations.switchMap(postDataSourceFactory.getMutableLiveData(), + (Function>) PostDataSource::getNetworkState);*/ + liveDataSource = postDataSourceFactory.getMutableLiveData(); + + PagedList.Config pagedListConfig = + (new PagedList.Config.Builder()) + .setEnablePlaceholders(false) + .setPageSize(25) + .build(); + + posts = (new LivePagedListBuilder(postDataSourceFactory, pagedListConfig)).build(); + } + + LiveData> getPosts() { return posts; } - void setPosts(ArrayList posts) { - this.posts.postValue(posts); - } + /*public LiveData getNetworkState() { + return networkState; + }*/ - void addPosts(ArrayList newPosts) { - ArrayList posts = this.posts.getValue(); - if(posts != null) { - posts.addAll(newPosts); - this.posts.postValue(posts); + public static class Factory extends ViewModelProvider.NewInstanceFactory { + private Retrofit retrofit; + private String accessToken; + private Locale locale; + private boolean isBestPost; + + public Factory(Retrofit retrofit, String accessToken, Locale locale, boolean isBestPost) { + this.retrofit = retrofit; + this.accessToken = accessToken; + this.locale = locale; + this.isBestPost = isBestPost; + } + + @NonNull + @Override + public T create(@NonNull Class modelClass) { + return (T) new PostViewModel(retrofit, accessToken, locale, isBestPost); } } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java index 27d152d1..6c8f209e 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java @@ -321,40 +321,6 @@ public class ViewPostDetailActivity extends AppCompatActivity { } queryComment(); - /*final Observable observable = Observable.create( - new ObservableOnSubscribe() { - @Override - public void subscribe(ObservableEmitter emitter) throws Exception { - emitter.onNext(mPost.getVoteType()); - emitter.onComplete(); - Log.i("asdasdf", "adasdfasdf"); - Toast.makeText(ViewPostDetailActivity.this, "observable", Toast.LENGTH_SHORT).show(); - } - } - ); - - final Observer observer = new Observer() { - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public void onNext(Object o) { - - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - Toast.makeText(ViewPostDetailActivity.this, "complete", Toast.LENGTH_SHORT).show(); - } - };*/ - mUpvoteButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { diff --git a/app/src/main/res/values-v28/styles.xml b/app/src/main/res/values-v28/styles.xml new file mode 100644 index 00000000..7b88cdf3 --- /dev/null +++ b/app/src/main/res/values-v28/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/build.gradle b/build.gradle index 35d0051f..8a7b6fa0 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,9 @@ allprojects { google() jcenter() maven { url 'https://jitpack.io' } + maven { url 'https://dl.bintray.com/sysdata/maven' } + maven { url 'http://repo.pageturner-reader.org' } + mavenCentral() } }