diff options
author | Alex Ning <chineseperson5@gmail.com> | 2019-07-21 16:09:40 +0000 |
---|---|---|
committer | Alex Ning <chineseperson5@gmail.com> | 2019-07-21 16:09:40 +0000 |
commit | 2f15543f913da5c9100ce114f4268c0576b70a3e (patch) | |
tree | 656429d2b712ffa391c7e7eb6de633a7f3408bce | |
parent | 0d5cf54af5de84b35a10ec0b4b2e12dbe09715a0 (diff) | |
download | infinity-for-reddit-2f15543f913da5c9100ce114f4268c0576b70a3e.tar infinity-for-reddit-2f15543f913da5c9100ce114f4268c0576b70a3e.tar.gz infinity-for-reddit-2f15543f913da5c9100ce114f4268c0576b70a3e.tar.bz2 infinity-for-reddit-2f15543f913da5c9100ce114f4268c0576b70a3e.tar.lz infinity-for-reddit-2f15543f913da5c9100ce114f4268c0576b70a3e.tar.xz infinity-for-reddit-2f15543f913da5c9100ce114f4268c0576b70a3e.tar.zst infinity-for-reddit-2f15543f913da5c9100ce114f4268c0576b70a3e.zip |
Add a ViewPager in ViewUserDetailActivity to display comments. Fixed searching posts without specifying subreddit will result in no posts. Use another api endpoint for fetching posts in ViewUserDetailActivity.
22 files changed, 1020 insertions, 52 deletions
diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser Binary files differindex 35e47bf8..1c28698e 100644 --- a/.idea/caches/build_file_checksums.ser +++ b/.idea/caches/build_file_checksums.ser diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser Binary files differindex 226d995d..cce3e832 100644 --- a/.idea/caches/gradle_models.ser +++ b/.idea/caches/gradle_models.ser diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java index 87c4f254..79aed4ef 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java @@ -24,4 +24,5 @@ interface AppComponent { void inject(PostVideoActivity postVideoActivity); void inject(FlairBottomSheetFragment flairBottomSheetFragment); void inject(RulesActivity rulesActivity); + void inject(CommentsListingFragment commentsListingFragment); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentAndPostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentAndPostRecyclerViewAdapter.java index 3bb3ef10..d6af8c03 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentAndPostRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentAndPostRecyclerViewAdapter.java @@ -442,7 +442,7 @@ class CommentAndPostRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie ((LoadMoreChildCommentsViewHolder) holder).placeholderTextView.setText(R.string.comment_load_more_comments); } } else if(holder instanceof LoadMoreCommentsFailedViewHolder) { - ((LoadMoreCommentsFailedViewHolder) holder).errorTextView.setText(R.string.post_load_comments_failed); + ((LoadMoreCommentsFailedViewHolder) holder).errorTextView.setText(R.string.load_comments_failed); } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSource.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSource.java new file mode 100644 index 00000000..acda274a --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSource.java @@ -0,0 +1,200 @@ +package ml.docilealligator.infinityforreddit; + +import android.os.AsyncTask; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; +import androidx.paging.PageKeyedDataSource; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Locale; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +public class CommentDataSource extends PageKeyedDataSource<String, CommentData> { + interface OnCommentFetchedCallback { + void hasComment(); + void noComment(); + } + + private Retrofit retrofit; + private Locale locale; + private String username; + private OnCommentFetchedCallback onCommentFetchedCallback; + + private MutableLiveData<NetworkState> paginationNetworkStateLiveData; + private MutableLiveData<NetworkState> initialLoadStateLiveData; + + private LoadInitialParams<String> initialParams; + private LoadInitialCallback<String, CommentData> initialCallback; + private LoadParams<String> params; + private LoadCallback<String, CommentData> callback; + + CommentDataSource(Retrofit retrofit, Locale locale, String username, OnCommentFetchedCallback onCommentFetchedCallback) { + this.retrofit = retrofit; + this.locale = locale; + this.username = username; + paginationNetworkStateLiveData = new MutableLiveData(); + initialLoadStateLiveData = new MutableLiveData(); + this.onCommentFetchedCallback = onCommentFetchedCallback; + } + + MutableLiveData getPaginationNetworkStateLiveData() { + return paginationNetworkStateLiveData; + } + + MutableLiveData getInitialLoadStateLiveData() { + return initialLoadStateLiveData; + } + + void retry() { + loadInitial(initialParams, initialCallback); + } + + void retryLoadingMore() { + loadAfter(params, callback); + } + + @Override + public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull LoadInitialCallback<String, CommentData> callback) { + initialParams = params; + initialCallback = callback; + + RedditAPI api = retrofit.create(RedditAPI.class); + Call<String> bestPost = api.getUserComments(username, null); + bestPost.enqueue(new Callback<String>() { + @Override + public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) { + if(response.isSuccessful()) { + new ParseCommentAsyncTask(response.body(), locale, new ParseCommentAsyncTask.ParseCommentAsyncTaskListener() { + @Override + public void parseSuccessful(ArrayList<CommentData> comments, String after) { + if(comments.size() == 0) { + onCommentFetchedCallback.noComment(); + } else { + onCommentFetchedCallback.hasComment(); + } + + callback.onResult(comments, null, after); + initialLoadStateLiveData.postValue(NetworkState.LOADED); + } + + @Override + public void parseFailed() { + Log.i("Comments fetch error", "Error parsing data"); + initialLoadStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error parsing data")); + } + }).execute(); + } else { + Log.i("Comments fetch error", "Error parsing data"); + initialLoadStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error parsing data")); + } + } + + @Override + public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) { + Log.i("Comments fetch error", "Error parsing data"); + initialLoadStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error parsing data")); + } + }); + } + + @Override + public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, CommentData> callback) { + + } + + @Override + public void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, CommentData> callback) { + this.params = params; + this.callback = callback; + + RedditAPI api = retrofit.create(RedditAPI.class); + Call<String> bestPost = api.getUserComments(username, params.key); + bestPost.enqueue(new Callback<String>() { + @Override + public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) { + if(response.isSuccessful()) { + new ParseCommentAsyncTask(response.body(), locale, new ParseCommentAsyncTask.ParseCommentAsyncTaskListener() { + @Override + public void parseSuccessful(ArrayList<CommentData> comments, String after) { + if(after == null || after.equals("") || after.equals("null")) { + callback.onResult(comments, null); + } else { + callback.onResult(comments, after); + } + paginationNetworkStateLiveData.postValue(NetworkState.LOADED); + } + + @Override + public void parseFailed() { + Log.i("Comments fetch error", "Error parsing data"); + paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error parsing data")); + } + }).execute(); + } else { + paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error parsing data")); + } + } + + @Override + public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) { + paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error parsing data")); + } + }); + } + + private static class ParseCommentAsyncTask extends AsyncTask<Void, ArrayList<CommentData>, ArrayList<CommentData>> { + private String response; + private String after; + private Locale locale; + private ParseCommentAsyncTaskListener parseCommentAsyncTaskListener; + + interface ParseCommentAsyncTaskListener { + void parseSuccessful(ArrayList<CommentData> comments, String after); + void parseFailed(); + } + + ParseCommentAsyncTask(String response, Locale locale, ParseCommentAsyncTaskListener parseCommentAsyncTaskListener) { + this.response = response; + this.locale = locale; + this.parseCommentAsyncTaskListener = parseCommentAsyncTaskListener; + } + + @Override + protected ArrayList<CommentData> doInBackground(Void... voids) { + try { + JSONObject data = new JSONObject(response).getJSONObject(JSONUtils.DATA_KEY); + JSONArray commentsJSONArray = data.getJSONArray(JSONUtils.CHILDREN_KEY); + ArrayList<CommentData> comments = new ArrayList<>(); + for(int i = 0; i < commentsJSONArray.length(); i++) { + JSONObject commentJSON = commentsJSONArray.getJSONObject(i).getJSONObject(JSONUtils.DATA_KEY); + comments.add(ParseComment.parseSingleComment(commentJSON, 0, locale)); + } + after = data.getString(JSONUtils.AFTER_KEY); + return comments; + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + + @Override + protected void onPostExecute(ArrayList<CommentData> commentData) { + super.onPostExecute(commentData); + if(commentData != null) { + parseCommentAsyncTaskListener.parseSuccessful(commentData, after); + } else { + parseCommentAsyncTaskListener.parseFailed(); + } + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSourceFactory.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSourceFactory.java new file mode 100644 index 00000000..ac43a9c2 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSourceFactory.java @@ -0,0 +1,43 @@ +package ml.docilealligator.infinityforreddit; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; +import androidx.paging.DataSource; + +import java.util.Locale; + +import retrofit2.Retrofit; + +class CommentDataSourceFactory extends DataSource.Factory { + private Retrofit retrofit; + private Locale locale; + private String username; + private CommentDataSource.OnCommentFetchedCallback onCommentFetchedCallback; + + private CommentDataSource commentDataSource; + private MutableLiveData<CommentDataSource> commentDataSourceLiveData; + + CommentDataSourceFactory(Retrofit retrofit, Locale locale, String username, CommentDataSource.OnCommentFetchedCallback onCommentFetchedCallback) { + this.retrofit = retrofit; + this.locale = locale; + this.username = username; + commentDataSourceLiveData = new MutableLiveData<>(); + this.onCommentFetchedCallback = onCommentFetchedCallback; + } + + @NonNull + @Override + public DataSource create() { + commentDataSource = new CommentDataSource(retrofit, locale, username, onCommentFetchedCallback); + commentDataSourceLiveData.postValue(commentDataSource); + return commentDataSource; + } + + public MutableLiveData<CommentDataSource> getCommentDataSourceLiveData() { + return commentDataSourceLiveData; + } + + CommentDataSource getCommentDataSource() { + return commentDataSource; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentViewModel.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentViewModel.java new file mode 100644 index 00000000..87fca11d --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentViewModel.java @@ -0,0 +1,83 @@ +package ml.docilealligator.infinityforreddit; + +import androidx.annotation.NonNull; +import androidx.arch.core.util.Function; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Transformations; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import androidx.paging.LivePagedListBuilder; +import androidx.paging.PagedList; + +import java.util.Locale; + +import retrofit2.Retrofit; + +public class CommentViewModel extends ViewModel { + private CommentDataSourceFactory commentDataSourceFactory; + private LiveData<NetworkState> paginationNetworkState; + private LiveData<NetworkState> initialLoadingState; + private LiveData<PagedList<CommentData>> comments; + + public CommentViewModel(Retrofit retrofit, Locale locale, String username, + CommentDataSource.OnCommentFetchedCallback onCommentFetchedCallback) { + commentDataSourceFactory = new CommentDataSourceFactory(retrofit, locale, username, onCommentFetchedCallback); + + initialLoadingState = Transformations.switchMap(commentDataSourceFactory.getCommentDataSourceLiveData(), + (Function<CommentDataSource, LiveData<NetworkState>>) CommentDataSource::getInitialLoadStateLiveData); + paginationNetworkState = Transformations.switchMap(commentDataSourceFactory.getCommentDataSourceLiveData(), + (Function<CommentDataSource, LiveData<NetworkState>>) CommentDataSource::getPaginationNetworkStateLiveData); + PagedList.Config pagedListConfig = + (new PagedList.Config.Builder()) + .setEnablePlaceholders(false) + .setPageSize(25) + .build(); + + comments = (new LivePagedListBuilder(commentDataSourceFactory, pagedListConfig)).build(); + } + + LiveData<PagedList<CommentData>> getComments() { + return comments; + } + + LiveData<NetworkState> getPaginationNetworkState() { + return paginationNetworkState; + } + + LiveData<NetworkState> getInitialLoadingState() { + return initialLoadingState; + } + + void refresh() { + commentDataSourceFactory.getCommentDataSource().invalidate(); + } + + void retry() { + commentDataSourceFactory.getCommentDataSource().retry(); + } + + void retryLoadingMore() { + commentDataSourceFactory.getCommentDataSource().retryLoadingMore(); + } + + public static class Factory extends ViewModelProvider.NewInstanceFactory { + private Retrofit retrofit; + private Locale locale; + private String username; + private CommentDataSource.OnCommentFetchedCallback onCommentFetchedCallback; + + public Factory(Retrofit retrofit, Locale locale, String username, + CommentDataSource.OnCommentFetchedCallback onCommentFetchedCallback) { + this.retrofit = retrofit; + this.locale = locale; + this.username = username; + this.onCommentFetchedCallback = onCommentFetchedCallback; + } + + @NonNull + @Override + public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { + return (T) new CommentViewModel(retrofit, locale, username, onCommentFetchedCallback); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingFragment.java new file mode 100644 index 00000000..5371d220 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingFragment.java @@ -0,0 +1,149 @@ +package ml.docilealligator.infinityforreddit; + + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; +import com.lsjwzh.widget.materialloadingprogressbar.CircleProgressBar; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; +import retrofit2.Retrofit; + + +/** + * A simple {@link Fragment} subclass. + */ +public class CommentsListingFragment extends Fragment implements FragmentCommunicator { + + static final String EXTRA_USERNAME_KEY = "ENK"; + + @BindView(R.id.coordinator_layout_comments_listing_fragment) CoordinatorLayout mCoordinatorLayout; + @BindView(R.id.recycler_view_comments_listing_fragment) RecyclerView mCommentRecyclerView; + @BindView(R.id.progress_bar_comments_listing_fragment) CircleProgressBar mProgressBar; + @BindView(R.id.fetch_comments_info_linear_layout_comments_listing_fragment) LinearLayout mFetchCommentInfoLinearLayout; + @BindView(R.id.fetch_comments_info_image_view_comments_listing_fragment) ImageView mFetchCommentInfoImageView; + @BindView(R.id.fetch_comments_info_text_view_comments_listing_fragment) TextView mFetchCommentInfoTextView; + + private RequestManager mGlide; + + private Activity activity; + + private CommentsListingRecyclerViewAdapter mAdapter; + + CommentViewModel mCommentViewModel; + + @Inject + @Named("no_oauth") + Retrofit mRetrofit; + + @Inject @Named("oauth") + Retrofit mOauthRetrofit; + + @Inject @Named("auth_info") + SharedPreferences mSharedPreferences; + + public CommentsListingFragment() { + // Required empty public constructor + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_comments_listing, container, false); + + ((Infinity) activity.getApplication()).getmAppComponent().inject(this); + + ButterKnife.bind(this, rootView); + + mGlide = Glide.with(activity); + + mCommentRecyclerView.setLayoutManager(new LinearLayoutManager(activity)); + + CommentViewModel.Factory factory; + mAdapter = new CommentsListingRecyclerViewAdapter(activity, mOauthRetrofit, + mSharedPreferences, () -> mCommentViewModel.retryLoadingMore()); + + String username = getArguments().getString(EXTRA_USERNAME_KEY); + + factory = new CommentViewModel.Factory(mRetrofit, getResources().getConfiguration().locale, + username, new CommentDataSource.OnCommentFetchedCallback() { + @Override + public void hasComment() { + mFetchCommentInfoLinearLayout.setVisibility(View.GONE); + } + + @Override + public void noComment() { + mFetchCommentInfoLinearLayout.setOnClickListener(view -> { + //Do nothing + }); + showErrorView(R.string.no_posts); + } + }); + + mCommentRecyclerView.setAdapter(mAdapter); + + mCommentViewModel = ViewModelProviders.of(this, factory).get(CommentViewModel.class); + mCommentViewModel.getComments().observe(this, comments -> mAdapter.submitList(comments)); + + mCommentViewModel.getInitialLoadingState().observe(this, networkState -> { + if(networkState.getStatus().equals(NetworkState.Status.SUCCESS)) { + mProgressBar.setVisibility(View.GONE); + } else if(networkState.getStatus().equals(NetworkState.Status.FAILED)) { + mFetchCommentInfoLinearLayout.setOnClickListener(view -> mCommentViewModel.retry()); + showErrorView(R.string.load_posts_error); + } else { + mFetchCommentInfoLinearLayout.setVisibility(View.GONE); + mProgressBar.setVisibility(View.VISIBLE); + } + }); + + mCommentViewModel.getPaginationNetworkState().observe(this, networkState -> { + mAdapter.setNetworkState(networkState); + }); + + return rootView; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + this.activity = (Activity) context; + } + + @Override + public void refresh() { + mCommentViewModel.refresh(); + } + + private void showErrorView(int stringResId) { + mProgressBar.setVisibility(View.GONE); + if(activity != null && isAdded()) { + mFetchCommentInfoLinearLayout.setVisibility(View.VISIBLE); + mFetchCommentInfoTextView.setText(stringResId); + mGlide.load(R.drawable.load_post_error_indicator).into(mFetchCommentInfoImageView); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingRecyclerViewAdapter.java new file mode 100644 index 00000000..8075c245 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingRecyclerViewAdapter.java @@ -0,0 +1,284 @@ +package ml.docilealligator.infinityforreddit; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; +import androidx.paging.PagedListAdapter; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import CustomView.CustomMarkwonView; +import butterknife.BindView; +import butterknife.ButterKnife; +import retrofit2.Retrofit; + +class CommentsListingRecyclerViewAdapter extends PagedListAdapter<CommentData, RecyclerView.ViewHolder> { + private Context mContext; + private Retrofit mOauthRetrofit; + private SharedPreferences mSharedPreferences; + + private static final int VIEW_TYPE_DATA = 0; + private static final int VIEW_TYPE_ERROR = 1; + private static final int VIEW_TYPE_LOADING = 2; + + private NetworkState networkState; + private RetryLoadingMoreCallback mRetryLoadingMoreCallback; + + interface RetryLoadingMoreCallback { + void retryLoadingMore(); + } + + protected CommentsListingRecyclerViewAdapter(Context context, Retrofit oauthRetrofit, SharedPreferences sharedPreferences, + RetryLoadingMoreCallback retryLoadingMoreCallback) { + super(DIFF_CALLBACK); + mContext = context; + mOauthRetrofit = oauthRetrofit; + mSharedPreferences = sharedPreferences; + mRetryLoadingMoreCallback = retryLoadingMoreCallback; + } + + private static final DiffUtil.ItemCallback<CommentData> DIFF_CALLBACK = new DiffUtil.ItemCallback<CommentData>() { + @Override + public boolean areItemsTheSame(@NonNull CommentData CommentData, @NonNull CommentData t1) { + return CommentData.getId().equals(t1.getId()); + } + + @Override + public boolean areContentsTheSame(@NonNull CommentData CommentData, @NonNull CommentData t1) { + return CommentData.getCommentContent().equals(t1.getCommentContent()); + } + }; + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if(viewType == VIEW_TYPE_DATA) { + CardView cardView = (CardView) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_comment, parent, false); + return new DataViewHolder(cardView); + } else if(viewType == VIEW_TYPE_ERROR) { + RelativeLayout relativeLayout = (RelativeLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer_error, parent, false); + return new ErrorViewHolder(relativeLayout); + } else { + RelativeLayout relativeLayout = (RelativeLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer_loading, parent, false); + return new LoadingViewHolder(relativeLayout); + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if(holder instanceof DataViewHolder) { + CommentData comment = getItem(holder.getAdapterPosition()); + + String authorPrefixed = "u/" + comment.getAuthor(); + ((DataViewHolder) holder).authorTextView.setText(authorPrefixed); + + ((DataViewHolder) holder).commentTimeTextView.setText(comment.getCommentTime()); + + ((DataViewHolder) holder).commentMarkdownView.setMarkdown(comment.getCommentContent(), mContext); + ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(comment.getScore())); + + switch (comment.getVoteType()) { + case 1: + ((DataViewHolder) holder).upvoteButton + .setColorFilter(ContextCompat.getColor(mContext, R.color.colorPrimary), android.graphics.PorterDuff.Mode.SRC_IN); + break; + case 2: + ((DataViewHolder) holder).downvoteButton + .setColorFilter(ContextCompat.getColor(mContext, R.color.minusButtonColor), android.graphics.PorterDuff.Mode.SRC_IN); + break; + } + } + } + + @Override + public int getItemViewType(int position) { + // Reached at the end + if (hasExtraRow() && position == getItemCount() - 1) { + if (networkState.getStatus() == NetworkState.Status.LOADING) { + return VIEW_TYPE_LOADING; + } else { + return VIEW_TYPE_ERROR; + } + } else { + return VIEW_TYPE_DATA; + } + } + + @Override + public int getItemCount() { + if(hasExtraRow()) { + return super.getItemCount() + 1; + } + return super.getItemCount(); + } + + private boolean hasExtraRow() { + return networkState != null && networkState.getStatus() != NetworkState.Status.SUCCESS; + } + + void setNetworkState(NetworkState newNetworkState) { + NetworkState previousState = this.networkState; + boolean previousExtraRow = hasExtraRow(); + this.networkState = newNetworkState; + boolean newExtraRow = hasExtraRow(); + if (previousExtraRow != newExtraRow) { + if (previousExtraRow) { + notifyItemRemoved(super.getItemCount()); + } else { + notifyItemInserted(super.getItemCount()); + } + } else if (newExtraRow && !previousState.equals(newNetworkState)) { + notifyItemChanged(getItemCount() - 1); + } + } + + class DataViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.vertical_block_item_post_comment) View verticalBlock; + @BindView(R.id.author_text_view_item_post_comment) TextView authorTextView; + @BindView(R.id.comment_time_text_view_item_post_comment) TextView commentTimeTextView; + @BindView(R.id.comment_markdown_view_item_post_comment) CustomMarkwonView commentMarkdownView; + @BindView(R.id.plus_button_item_post_comment) ImageView upvoteButton; + @BindView(R.id.score_text_view_item_post_comment) TextView scoreTextView; + @BindView(R.id.minus_button_item_post_comment) ImageView downvoteButton; + @BindView(R.id.share_button_item_post_comment) ImageView shareButton; + @BindView(R.id.reply_button_item_post_comment) ImageView replyButton; + + DataViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + + verticalBlock.setVisibility(View.GONE); + + authorTextView.setOnClickListener(view -> { + Intent intent = new Intent(mContext, ViewUserDetailActivity.class); + intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, getItem(getAdapterPosition()).getAuthor()); + mContext.startActivity(intent); + }); + + RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) shareButton.getLayoutParams(); + lp.addRule(RelativeLayout.ALIGN_PARENT_END); + lp.setMarginEnd(0); + shareButton.setLayoutParams(lp); + + shareButton.setOnClickListener(view -> { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + String extraText = getItem(getAdapterPosition()).getPermalink(); + intent.putExtra(Intent.EXTRA_TEXT, extraText); + mContext.startActivity(Intent.createChooser(intent, "Share")); + }); + + replyButton.setVisibility(View.GONE); + + upvoteButton.setOnClickListener(view -> { + int previousVoteType = getItem(getAdapterPosition()).getVoteType(); + String newVoteType; + + downvoteButton.clearColorFilter(); + + if(previousVoteType != CommentData.VOTE_TYPE_UPVOTE) { + //Not upvoted before + getItem(getAdapterPosition()).setVoteType(CommentData.VOTE_TYPE_UPVOTE); + newVoteType = RedditUtils.DIR_UPVOTE; + upvoteButton.setColorFilter(ContextCompat.getColor(mContext, R.color.backgroundColorPrimaryDark), android.graphics.PorterDuff.Mode.SRC_IN); + } else { + //Upvoted before + getItem(getAdapterPosition()).setVoteType(CommentData.VOTE_TYPE_NO_VOTE); + newVoteType = RedditUtils.DIR_UNVOTE; + upvoteButton.clearColorFilter(); + } + + scoreTextView.setText(Integer.toString(getItem(getAdapterPosition()).getScore() + getItem(getAdapterPosition()).getVoteType())); + + VoteThing.voteThing(mOauthRetrofit, mSharedPreferences, new VoteThing.VoteThingListener() { + @Override + public void onVoteThingSuccess(int position) { + if(newVoteType.equals(RedditUtils.DIR_UPVOTE)) { + getItem(getAdapterPosition()).setVoteType(CommentData.VOTE_TYPE_UPVOTE); + upvoteButton.setColorFilter(ContextCompat.getColor(mContext, R.color.backgroundColorPrimaryDark), android.graphics.PorterDuff.Mode.SRC_IN); + } else { + getItem(getAdapterPosition()).setVoteType(CommentData.VOTE_TYPE_NO_VOTE); + upvoteButton.clearColorFilter(); + } + + downvoteButton.clearColorFilter(); + scoreTextView.setText(Integer.toString(getItem(getAdapterPosition()).getScore() + getItem(getAdapterPosition()).getVoteType())); + } + + @Override + public void onVoteThingFail(int position) { } + }, getItem(getAdapterPosition()).getFullName(), newVoteType, getAdapterPosition()); + }); + + downvoteButton.setOnClickListener(view -> { + int previousVoteType = getItem(getAdapterPosition()).getVoteType(); + String newVoteType; + + upvoteButton.clearColorFilter(); + + if(previousVoteType != CommentData.VOTE_TYPE_DOWNVOTE) { + //Not downvoted before + getItem(getAdapterPosition()).setVoteType(CommentData.VOTE_TYPE_DOWNVOTE); + newVoteType = RedditUtils.DIR_DOWNVOTE; + downvoteButton.setColorFilter(ContextCompat.getColor(mContext, R.color.colorAccent), android.graphics.PorterDuff.Mode.SRC_IN); + } else { + //Downvoted before + getItem(getAdapterPosition()).setVoteType(CommentData.VOTE_TYPE_NO_VOTE); + newVoteType = RedditUtils.DIR_UNVOTE; + downvoteButton.clearColorFilter(); + } + + scoreTextView.setText(Integer.toString(getItem(getAdapterPosition()).getScore() + getItem(getAdapterPosition()).getVoteType())); + + VoteThing.voteThing(mOauthRetrofit, mSharedPreferences, new VoteThing.VoteThingListener() { + @Override + public void onVoteThingSuccess(int position1) { + if(newVoteType.equals(RedditUtils.DIR_DOWNVOTE)) { + getItem(getAdapterPosition()).setVoteType(CommentData.VOTE_TYPE_DOWNVOTE); + downvoteButton.setColorFilter(ContextCompat.getColor(mContext, R.color.colorAccent), android.graphics.PorterDuff.Mode.SRC_IN); + } else { + getItem(getAdapterPosition()).setVoteType(CommentData.VOTE_TYPE_NO_VOTE); + downvoteButton.clearColorFilter(); + } + + upvoteButton.clearColorFilter(); + scoreTextView.setText(Integer.toString(getItem(getAdapterPosition()).getScore() + getItem(getAdapterPosition()).getVoteType())); + } + + @Override + public void onVoteThingFail(int position1) { } + }, getItem(getAdapterPosition()).getFullName(), newVoteType, getAdapterPosition()); + }); + } + } + + class ErrorViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.error_text_view_item_footer_error) TextView errorTextView; + @BindView(R.id.retry_button_item_footer_error) Button retryButton; + + ErrorViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + errorTextView.setText(R.string.load_comments_failed); + retryButton.setOnClickListener(view -> mRetryLoadingMoreCallback.retryLoadingMore()); + } + } + + class LoadingViewHolder extends RecyclerView.ViewHolder { + LoadingViewHolder(@NonNull View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java index 7f92697a..9240ccd3 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java @@ -5,25 +5,20 @@ package ml.docilealligator.infinityforreddit; */ public class JSONUtils { - public static final String DATA_KEY = "data"; + static final String DATA_KEY = "data"; static final String AFTER_KEY = "after"; - static final String MODHASH_KEY = "modhash"; static final String CHILDREN_KEY = "children"; static final String COUNT_KEY = "count"; static final String TITLE_KEY = "title"; - public static final String NAME_KEY = "name"; + static final String NAME_KEY = "name"; static final String SUBREDDIT_NAME_PREFIX_KEY = "subreddit_name_prefixed"; static final String SELFTEXT_HTML_KEY = "selftext_html"; static final String AUTHOR_KEY = "author"; - static final String DOMAIN_KEY = "domain"; static final String LINK_FLAIR_TEXT_KEY = "link_flair_text"; - static final String NUM_CROSSPOST_KEY = "num_crossposts"; - static final String CAN_MOD_POST_KEY = "can_mod_post"; static final String SCORE_KEY = "score"; static final String LIKES_KEY = "likes"; static final String NSFW_KEY = "over_18"; static final String GILDED_KEY = "gilded"; - static final String POST_HINT_KEY = "post_hint"; static final String PERMALINK_KEY = "permalink"; static final String CREATED_UTC_KEY = "created_utc"; static final String PREVIEW_KEY = "preview"; @@ -37,12 +32,10 @@ public class JSONUtils { static final String URL_KEY = "url"; static final String MEDIA_KEY = "media"; static final String REDDIT_VIDEO_KEY = "reddit_video"; - static final String FALLBACK_URL_KEY = "fallback_url"; static final String DASH_URL_KEY = "dash_url"; static final String IS_VIDEO_KEY = "is_video"; static final String CROSSPOST_PARENT_LIST = "crosspost_parent_list"; static final String REDDIT_VIDEO_PREVIEW_KEY = "reddit_video_preview"; - static final String IS_REDDIT_MEDIA_DOMAIN = "is_reddit_media_domain"; static final String STICKIED_KEY = "stickied"; static final String BODY_HTML_KEY = "body_html"; static final String COLLAPSED_KEY = "collapsed"; @@ -51,24 +44,22 @@ public class JSONUtils { static final String DEPTH_KEY = "depth"; static final String ID_KEY = "id"; static final String SCORE_HIDDEN_KEY = "score_hidden"; - public static final String SUBREDDIT_KEY = "subreddit"; - public static final String BANNER_IMG_KEY = "banner_img"; + static final String SUBREDDIT_KEY = "subreddit"; + static final String BANNER_IMG_KEY = "banner_img"; static final String BANNER_BACKGROUND_IMAGE_KEY = "banner_background_image"; - public static final String ICON_IMG_KEY = "icon_img"; + static final String ICON_IMG_KEY = "icon_img"; static final String COMMUNITY_ICON_KEY = "community_icon"; - public static final String LINK_KARMA_KEY = "link_karma"; - public static final String COMMENT_KARMA_KEY = "comment_karma"; + static final String LINK_KARMA_KEY = "link_karma"; + static final String COMMENT_KARMA_KEY = "comment_karma"; static final String DISPLAY_NAME = "display_name"; static final String SUBREDDIT_TYPE_KEY = "subreddit_type"; static final String SUBREDDIT_TYPE_VALUE_USER = "user"; static final String SUBSCRIBERS_KEY = "subscribers"; static final String PUBLIC_DESCRIPTION_KEY = "public_description"; static final String ACTIVE_USER_COUNT_KEY = "active_user_count"; - public static final String IS_GOLD_KEY = "is_gold"; - public static final String IS_FRIEND_KEY = "is_friend"; - static final String KIND_KEY = "kind"; + static final String IS_GOLD_KEY = "is_gold"; + static final String IS_FRIEND_KEY = "is_friend"; static final String JSON_KEY = "json"; - static final String THINGS_KEY = "things"; static final String PARENT_ID_KEY = "parent_id"; static final String ERRORS_KEY = "errors"; static final String ARGS_KEY = "args"; diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java index 97170b5b..cb6b8acc 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java @@ -211,7 +211,7 @@ class ParseComment { } } - private static CommentData parseSingleComment(JSONObject singleCommentData, int depth, Locale locale) throws JSONException { + static CommentData parseSingleComment(JSONObject singleCommentData, int depth, Locale locale) throws JSONException { String id = singleCommentData.getString(JSONUtils.ID_KEY); String fullName = singleCommentData.getString(JSONUtils.NAME_KEY); String author = singleCommentData.getString(JSONUtils.AUTHOR_KEY); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ParsePost.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ParsePost.java index 817fd7c8..a2146668 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/ParsePost.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ParsePost.java @@ -90,15 +90,8 @@ class ParsePost { return null; } - String kind = allData.getJSONObject(0).getString(JSONUtils.KIND_KEY); - if(kind.equals("t3")) { - //It's a post - JSONObject data = allData.getJSONObject(0).getJSONObject(JSONUtils.DATA_KEY); - post = parseBasicData(data, locale, -1); - } else { - parseFailed = true; - return null; - } + JSONObject data = allData.getJSONObject(0).getJSONObject(JSONUtils.DATA_KEY); + post = parseBasicData(data, locale, -1); } else { //Posts listing int size; @@ -109,12 +102,8 @@ class ParsePost { } for(int i = 0; i < size; i++) { - String kind = allData.getJSONObject(i).getString(JSONUtils.KIND_KEY); - if(kind.equals("t3")) { - //It's a post - JSONObject data = allData.getJSONObject(i).getJSONObject(JSONUtils.DATA_KEY); - newPosts.add(parseBasicData(data, locale, i)); - } + JSONObject data = allData.getJSONObject(i).getJSONObject(JSONUtils.DATA_KEY); + newPosts.add(parseBasicData(data, locale, i)); } } } catch (JSONException e) { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java index 84bf4c88..2f5c882e 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java @@ -190,7 +190,11 @@ class PostDataSource extends PageKeyedDataSource<String, Post> { ParsePost.parsePosts(response.body(), locale, -1, new ParsePost.ParsePostsListingListener() { @Override public void onParsePostsListingSuccess(ArrayList<Post> newPosts, String lastItem) { - callback.onResult(newPosts, lastItem); + if(lastItem == null || lastItem.equals("") || lastItem.equals("null")) { + callback.onResult(newPosts, null); + } else { + callback.onResult(newPosts, lastItem); + } paginationNetworkStateLiveData.postValue(NetworkState.LOADED); } @@ -265,7 +269,11 @@ class PostDataSource extends PageKeyedDataSource<String, Post> { ParsePost.parsePosts(response.body(), locale, -1, new ParsePost.ParsePostsListingListener() { @Override public void onParsePostsListingSuccess(ArrayList<Post> newPosts, String lastItem) { - callback.onResult(newPosts, lastItem); + if(lastItem == null || lastItem.equals("") || lastItem.equals("null")) { + callback.onResult(newPosts, null); + } else { + callback.onResult(newPosts, lastItem); + } paginationNetworkStateLiveData.postValue(NetworkState.LOADED); } @@ -378,7 +386,7 @@ class PostDataSource extends PageKeyedDataSource<String, Post> { Call<String> getPost; if(subredditName == null) { - getPost = api.searchPosts(subredditName, null, RedditUtils.getOAuthHeader(accessToken)); + getPost = api.searchPosts(query, null, RedditUtils.getOAuthHeader(accessToken)); } else { getPost = api.searchPostsInSpecificSubreddit(subredditName, query, null, RedditUtils.getOAuthHeader(accessToken)); } @@ -438,7 +446,11 @@ class PostDataSource extends PageKeyedDataSource<String, Post> { ParsePost.parsePosts(response.body(), locale, -1, new ParsePost.ParsePostsListingListener() { @Override public void onParsePostsListingSuccess(ArrayList<Post> newPosts, String lastItem) { - callback.onResult(newPosts, lastItem); + if(lastItem == null || lastItem.equals("") || lastItem.equals("null")) { + callback.onResult(newPosts, null); + } else { + callback.onResult(newPosts, lastItem); + } paginationNetworkStateLiveData.postValue(NetworkState.LOADED); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java index 4efc649f..d8a86141 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java @@ -48,13 +48,16 @@ public interface RedditAPI { Call<String> getSubredditBestPosts(@Path("subredditName") String subredditName, @Query("after") String lastItem, @HeaderMap Map<String, String> headers); - @GET("user/{userName}.json?raw_json=1&limit=25") - Call<String> getUserBestPosts(@Path("userName") String userName, @Query("after") String lastItem, + @GET("user/{username}/submitted.json?raw_json=1&limit=25") + Call<String> getUserBestPosts(@Path("username") String username, @Query("after") String lastItem, @HeaderMap Map<String, String> headers); @GET("user/{username}/about.json?raw_json=1") Call<String> getUserData(@Path("username") String username); + @GET("user/{username}/comments.json?raw_json=1") + Call<String> getUserComments(@Path("username") String username, @Query("after") String after); + @FormUrlEncoded @POST("api/subscribe") Call<String> subredditSubscription(@HeaderMap Map<String, String> headers, @FieldMap Map<String, String> params); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SearchActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SearchActivity.java index 3600bd27..725b50a0 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/SearchActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SearchActivity.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; +import android.view.WindowManager; import android.widget.RelativeLayout; import android.widget.TextView; @@ -99,6 +100,14 @@ public class SearchActivity extends AppCompatActivity { } @Override + protected void onStart() { + super.onStart(); + simpleSearchView.showSearch(false); + simpleSearchView.getSearchEditText().requestFocus(); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (simpleSearchView.onActivityResult(requestCode, resultCode, data)) { return; diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingRecyclerViewAdapter.java index 6adaecef..4de52062 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingRecyclerViewAdapter.java @@ -208,7 +208,7 @@ public class SubredditListingRecyclerViewAdapter extends PagedListAdapter<Subred super(itemView); ButterKnife.bind(this, itemView); retryButton.setOnClickListener(view -> retryLoadingMoreCallback.retryLoadingMore()); - errorTextView.setText(R.string.post_load_comments_failed); + errorTextView.setText(R.string.load_comments_failed); } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/UserListingRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/UserListingRecyclerViewAdapter.java index a67f7415..aa722fc6 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/UserListingRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/UserListingRecyclerViewAdapter.java @@ -208,7 +208,7 @@ public class UserListingRecyclerViewAdapter extends PagedListAdapter<UserData, R super(itemView); ButterKnife.bind(this, itemView); retryButton.setOnClickListener(view -> retryLoadingMoreCallback.retryLoadingMore()); - errorTextView.setText(R.string.post_load_comments_failed); + errorTextView.setText(R.string.load_comments_failed); } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewUserDetailActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewUserDetailActivity.java index b08f0831..66e6257f 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewUserDetailActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewUserDetailActivity.java @@ -15,7 +15,10 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; import androidx.lifecycle.ViewModelProviders; +import androidx.viewpager.widget.ViewPager; import com.bumptech.glide.Glide; import com.bumptech.glide.RequestManager; @@ -24,6 +27,7 @@ import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.chip.Chip; import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.tabs.TabLayout; import javax.inject.Inject; import javax.inject.Named; @@ -48,7 +52,9 @@ public class ViewUserDetailActivity extends AppCompatActivity { private static final String IS_IN_LAZY_MODE_STATE = "IILMS"; @BindView(R.id.coordinator_layout_view_user_detail_activity) CoordinatorLayout coordinatorLayout; + @BindView(R.id.view_pager_view_user_detail_activity) ViewPager viewPager; @BindView(R.id.appbar_layout_view_user_detail) AppBarLayout appBarLayout; + @BindView(R.id.tab_layout_view_user_detail_activity) TabLayout tabLayout; @BindView(R.id.collapsing_toolbar_layout_view_user_detail_activity) CollapsingToolbarLayout collapsingToolbarLayout; @BindView(R.id.banner_image_view_view_user_detail_activity) GifImageView bannerImageView; @BindView(R.id.icon_gif_image_view_view_user_detail_activity) GifImageView iconGifImageView; @@ -56,6 +62,8 @@ public class ViewUserDetailActivity extends AppCompatActivity { @BindView(R.id.subscribe_user_chip_view_user_detail_activity) Chip subscribeUserChip; @BindView(R.id.karma_text_view_view_user_detail_activity) TextView karmaTextView; + private SectionsPagerAdapter sectionsPagerAdapter; + private Fragment mFragment; private SubscribedUserDao subscribedUserDao; private RequestManager glide; @@ -66,6 +74,8 @@ public class ViewUserDetailActivity extends AppCompatActivity { private String userName; private boolean subscriptionReady = false; private boolean isInLazyMode = false; + private int colorPrimary; + private int white; @Inject @Named("no_oauth") @@ -108,6 +118,29 @@ public class ViewUserDetailActivity extends AppCompatActivity { ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams(); params.topMargin = statusBarHeight; + sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); + viewPager.setAdapter(sectionsPagerAdapter); + viewPager.setOffscreenPageLimit(2); + tabLayout.setupWithViewPager(viewPager); + + colorPrimary = getResources().getColor(R.color.colorPrimary); + white = getResources().getColor(android.R.color.white); + + appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { + @Override + void onStateChanged(AppBarLayout appBarLayout, State state) { + if(state == State.EXPANDED) { + tabLayout.setTabTextColors(colorPrimary, colorPrimary); + tabLayout.setSelectedTabIndicatorColor(colorPrimary); + tabLayout.setBackgroundColor(white); + } else if(state == State.COLLAPSED) { + tabLayout.setTabTextColors(white, white); + tabLayout.setSelectedTabIndicatorColor(white); + tabLayout.setBackgroundColor(colorPrimary); + } + } + }); + subscribedUserDao = SubscribedUserRoomDatabase.getDatabase(this).subscribedUserDao(); glide = Glide.with(this); @@ -238,23 +271,23 @@ public class ViewUserDetailActivity extends AppCompatActivity { }); if(savedInstanceState == null) { - mFragment = new PostFragment(); + /*mFragment = new PostFragment(); Bundle bundle = new Bundle(); bundle.putString(PostFragment.EXTRA_SUBREDDIT_NAME_KEY, userName); bundle.putInt(PostFragment.EXTRA_POST_TYPE_KEY, PostDataSource.TYPE_USER); mFragment.setArguments(bundle); - getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout_view_user_detail_activity, mFragment).commit(); + getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout_view_user_detail_activity, mFragment).commit();*/ } else { - mFragment = getSupportFragmentManager().getFragment(savedInstanceState, FRAGMENT_OUT_STATE_KEY); + /*mFragment = getSupportFragmentManager().getFragment(savedInstanceState, FRAGMENT_OUT_STATE_KEY); if(mFragment == null) { mFragment = new PostFragment(); Bundle bundle = new Bundle(); bundle.putString(PostFragment.EXTRA_SUBREDDIT_NAME_KEY, userName); bundle.putInt(PostFragment.EXTRA_POST_TYPE_KEY, PostDataSource.TYPE_USER); mFragment.setArguments(bundle); - } + }*/ isInLazyMode = savedInstanceState.getBoolean(IS_IN_LAZY_MODE_STATE); - getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout_view_user_detail_activity, mFragment).commit(); + //getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout_view_user_detail_activity, mFragment).commit(); } } @@ -321,13 +354,51 @@ public class ViewUserDetailActivity extends AppCompatActivity { protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(IS_IN_LAZY_MODE_STATE, isInLazyMode); - getSupportFragmentManager().putFragment(outState, FRAGMENT_OUT_STATE_KEY, mFragment); + //getSupportFragmentManager().putFragment(outState, FRAGMENT_OUT_STATE_KEY, mFragment); } private void makeSnackbar(int resId) { Snackbar.make(coordinatorLayout, resId, Snackbar.LENGTH_SHORT).show(); } + public abstract static class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener { + // State + public enum State { + EXPANDED, + COLLAPSED, + IDLE + } + + private State mCurrentState = State.IDLE; + + @Override + public final void onOffsetChanged(AppBarLayout appBarLayout, int i) { + if (i == 0) { + if (mCurrentState != State.EXPANDED) { + onStateChanged(appBarLayout, State.EXPANDED); + } + mCurrentState = State.EXPANDED; + } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) { + if (mCurrentState != State.COLLAPSED) { + onStateChanged(appBarLayout, State.COLLAPSED); + } + mCurrentState = State.COLLAPSED; + } else { + if (mCurrentState != State.IDLE) { + onStateChanged(appBarLayout, State.IDLE); + } + mCurrentState = State.IDLE; + } + } + + /** + * Notifies on state change + * @param appBarLayout Layout + * @param state Collapse state + */ + abstract void onStateChanged(AppBarLayout appBarLayout, State state); + } + private static class InsertUserDataAsyncTask extends AsyncTask<Void, Void, Void> { private UserDao userDao; @@ -344,4 +415,71 @@ public class ViewUserDetailActivity extends AppCompatActivity { return null; } } + + private class SectionsPagerAdapter extends FragmentPagerAdapter { + private PostFragment postFragment; + private CommentsListingFragment commentsListingFragment; + + SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + @NonNull + @Override + public Fragment getItem(int position) { + if (position == 0) { + PostFragment fragment = new PostFragment(); + Bundle bundle = new Bundle(); + bundle.putInt(PostFragment.EXTRA_POST_TYPE_KEY, PostDataSource.TYPE_USER); + bundle.putString(PostFragment.EXTRA_SUBREDDIT_NAME_KEY, userName); + fragment.setArguments(bundle); + return fragment; + } + CommentsListingFragment fragment = new CommentsListingFragment(); + Bundle bundle = new Bundle(); + bundle.putString(CommentsListingFragment.EXTRA_USERNAME_KEY, userName); + fragment.setArguments(bundle); + return fragment; + } + + @Override + public int getCount() { + return 2; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case 0: + return "Posts"; + case 1: + return "Comments"; + } + return null; + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + Fragment fragment = (Fragment) super.instantiateItem(container, position); + switch (position) { + case 0: + postFragment = (PostFragment) fragment; + break; + case 1: + commentsListingFragment = (CommentsListingFragment) fragment; + break; + } + return fragment; + } + + public void refresh() { + if(postFragment != null) { + ((FragmentCommunicator) postFragment).refresh(); + } + if(commentsListingFragment != null) { + ((FragmentCommunicator) commentsListingFragment).refresh(); + } + } + } } diff --git a/app/src/main/res/layout/activity_view_user_detail.xml b/app/src/main/res/layout/activity_view_user_detail.xml index fac8a2ca..67beecd2 100644 --- a/app/src/main/res/layout/activity_view_user_detail.xml +++ b/app/src/main/res/layout/activity_view_user_detail.xml @@ -7,6 +7,12 @@ android:id="@+id/coordinator_layout_view_user_detail_activity" tools:context=".ViewUserDetailActivity"> + <androidx.viewpager.widget.ViewPager + android:id="@+id/view_pager_view_user_detail_activity" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + <com.google.android.material.appbar.AppBarLayout android:id="@+id/appbar_layout_view_user_detail" android:layout_width="match_parent" @@ -94,8 +100,20 @@ </com.google.android.material.appbar.CollapsingToolbarLayout> + <com.google.android.material.tabs.TabLayout + android:id="@+id/tab_layout_view_user_detail_activity" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + app:layout_scrollFlags="scroll|enterAlways" + app:tabGravity="fill" + app:tabMode="fixed" + app:tabIndicatorHeight="3dp" + app:tabRippleColor="?attr/colorControlHighlight" + app:tabUnboundedRipple="false" /> + </com.google.android.material.appbar.AppBarLayout> - <include layout="@layout/content_view_user_detail" /> + <!--<include layout="@layout/content_view_user_detail" />--> </androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_comments_listing.xml b/app/src/main/res/layout/fragment_comments_listing.xml new file mode 100644 index 00000000..bac6f507 --- /dev/null +++ b/app/src/main/res/layout/fragment_comments_listing.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/coordinator_layout_comments_listing_fragment" + tools:context="ml.docilealligator.infinityforreddit.CommentsListingFragment"> + + <com.lsjwzh.widget.materialloadingprogressbar.CircleProgressBar + android:id="@+id/progress_bar_comments_listing_fragment" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" + app:mlpb_progress_stoke_width="3dp" + app:mlpb_progress_color="@color/colorAccent" + app:mlpb_background_color="@color/circularProgressBarBackground" + android:layout_gravity="center_horizontal"/> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recycler_view_comments_listing_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <LinearLayout + android:id="@+id/fetch_comments_info_linear_layout_comments_listing_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:orientation="vertical" + android:visibility="gone"> + + <ImageView + android:id="@+id/fetch_comments_info_image_view_comments_listing_fragment" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/fetch_comments_info_text_view_comments_listing_fragment" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:gravity="center" /> + + </LinearLayout> + +</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/item_load_comments_failed_placeholder.xml b/app/src/main/res/layout/item_load_comments_failed_placeholder.xml index d81cdb97..f699efd0 100644 --- a/app/src/main/res/layout/item_load_comments_failed_placeholder.xml +++ b/app/src/main/res/layout/item_load_comments_failed_placeholder.xml @@ -15,6 +15,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:text="@string/post_load_comments_failed"/> + android:text="@string/load_comments_failed"/> </LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ba9c2924..76e3f626 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,7 +33,7 @@ <string name="no_storage_permission">No storage permission to save this file</string> <string name="load_posts_failed">Error loading posts</string> - <string name="post_load_comments_failed">Error loading comments</string> + <string name="load_comments_failed">Error loading comments</string> <string name="retry">Retry</string> <string name="comments">Comments</string> <string name="no_comments_yet">No comments yet. Write a comment?</string> |